Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented interval arithmetic. #2682

Closed
wants to merge 5 commits into from

Conversation

hargup
Copy link
Contributor

@hargup hargup commented Dec 15, 2013

  • Allow additions and multiplications and other basic operations with intervals
  • Allow elementary functions to take intervals as arguments
  • Add tests
  • Add documentation

@asmeurer
Copy link
Member

I would use a separate class from Interval. For instance, * already means cartesian product on Interval. You can use Interval internally if it takes care of some calculations for you.

@hargup
Copy link
Contributor Author

hargup commented Dec 18, 2013

I would do it. But I think it end up being the same class Interval with little different name and different meaning for the arithmetic operators. I think it will be little confusing for us to have things like that. Do you have any workarounds?

@asmeurer
Copy link
Member

Maybe what you really want is a separate class on top of Interval. What you are building isn't really an interval, but rather an arbitrary element of an interval. [1, 2] + [2, 4) doesn't make sense mathematically, but (arbitrary element of [1, 2]) + (arbitrary element of [2, 4)) does.

Anyway, we should definitely keep them separate. Interval is a set, not an expression (in the Expr sense). We often use abuse of notation to treat intervals as such, but abuses of notation only serve to create confusing type systems (which isn't to say we shouldn't allow nice syntactic sugar; that's a separate point).

@hargup
Copy link
Contributor Author

hargup commented Dec 20, 2013

Maybe what you really want is a separate class on top of Interval. What you are building isn't really an interval, but rather an arbitrary element of an interval. [1, 2] + [2, 4) doesn't make sense mathematically, but (arbitrary element of [1, 2]) + (arbitrary element of [2, 4)) does.

Anyway, we should definitely keep them separate. Interval is a set, not an expression (in the Expr sense). We often use abuse of notation to treat intervals as such, but abuses of notation only serve to create confusing type systems (which isn't to say we shouldn't allow nice syntactic sugar; that's a separate point).

Thanks, I got it now. I have created a separate class IV for the purpose. By the way should I put it in a file other than sets.py.

For some reasons the test for division of intervals are failing on my local machine, but it working fine on IPython. Can you help me with this?

================================================================ test process starts =================================================================
executable:         /usr/bin/python  (2.7.3-final-0) [CPython]
architecture:       64-bit
cache:              yes
ground types:       gmpy 1.17
random seed:        51926891
hash randomization: on (PYTHONHASHSEED=3725838702)

sympy/core/tests/test_sets.py[35] ..................................E                                                                           [FAIL]

______________________________________________________________________________________________________________________________________________________
_______________________________________________________ sympy/core/tests/test_sets.py:test_IV ________________________________________________________
  File "/home/hargup/Harsh-sympy/sympy/core/tests/test_sets.py", line 569, in test_IV
    assert IV(1, 2)/2 == IV(Rational(1, 2), 1)
TypeError: unsupported operand type(s) for /: 'IV' and 'int'

============================================== tests finished: 34 passed, 1 exceptions, in 0.28 seconds ==============================================
DO *NOT* COMMIT!
In [1]: from sympy import *

In [2]: IV(1, 2)/IV(2, 3)
Out[2]: [1/3, 1]

In [3]: IV(1, 2)/2
Out[3]: [1/2, 1]

@skirpichev
Copy link
Contributor

By the way should I put it in a file other than sets.py.

in numbers.py, m.b.?

Can you help me with this?

Yes, the problem is here:

TypeError: unsupported operand type(s) for /: 'IV' and 'int'

Please, take look on the python documentation & other classes (e.g. numbers) on how to handle this. Or you can change 2 -> Integer(2).

@hargup
Copy link
Contributor Author

hargup commented Dec 22, 2013

Yes, the problem is here:

TypeError: unsupported operand type(s) for /: 'IV' and 'int'

Please, take look on the python documentation & other classes (e.g. numbers) on how to handle this. Or you can change 2 -> Integer(2).

Thanks, I had defined __div__ and __rdiv__ but not __truediv__ and __rtruediv__. That's why I was getting error when running the tests but not when I was doing the same thing with in IPython console.

@asmeurer
Copy link
Member

Yes, there are three combinations to test, Python 2, Python 2 with from __future__ import division, and Python 3. The first one is not important, because every file should be importing division. The last two should be almost the same, but I think there may be some differences regarding __truediv__.

@skirpichev
Copy link
Contributor

On Sun, Dec 22, 2013 at 03:19:09PM -0800, Aaron Meurer wrote:

Yes, there are three combinations to test, Python 2, Python 2 with from
future import division, and Python 3. The first one is not important,
because every file should be importing division. The last two should be
almost the same, but I think there may be some differences regarding
truediv.

Why not add just:
truediv = div
(like other numbers.py's classes)?

@hargup
Copy link
Contributor Author

hargup commented Dec 25, 2013

@skirpichev: I created a new file instead of adding IV class in the numbers.py file. I did this because in numbers.py file I was not able to import Interval from sets.py probably because it was in in turn importing a lot of classes from numbers.py.

@hargup
Copy link
Contributor Author

hargup commented Dec 25, 2013

@asmeurer, @skirpichev : You can start the review. I hope this time you won't have to write comments like "space after comma".

from sympy.core.singleton import S


class IV(Interval, Expr):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Expr? (and not even AtomicExpr)

@skirpichev
Copy link
Contributor

I did this because in numbers.py file I was not able to import Interval from sets.py probably because it was in in turn importing a lot of classes from numbers.py.

I don't think it's a good idea to introduce a new file. Logically, IV belongs to numbers.py or just to sets.py.

And for @asmeurer.

Maybe what you really want is a separate class on top of Interval. What you are building isn't really an interval, > but rather an arbitrary element of an interval.

No, it is. All we do - define some new fancy binary ops: Interval x Interval -> Interval.

Anyway, we should definitely keep them separate. Interval is a set, not an expression (in the Expr sense). We > often use abuse of notation to treat intervals as such, but abuses of notation only serve to create confusing
type systems (which isn't to say we shouldn't allow nice syntactic sugar; that's a separate point).

And maybe the current Set class is wrong. Probably, it should be like builtin set, and not to overload add as union, for example. See this:
http://docs.python.org/3.4/library/stdtypes.html#set-types-set-frozenset

Interval - is just a very good name to keep it. If we keep clean separate interfaces (set-like and number-like) - nothing is wrong if we will have an entity that we can treat as a set and as an expression.

Mul(self.end, other.end)))

elif Number.is_real:
if other > S.Zero:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if other.is_positive

@asmeurer
Copy link
Member

It doesn't seem to belong to numbers.py to me.

Mul(self.end, 1/other.end)))

elif other.is_real:
if other > S.Zero:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other.is_positive

and so on, as above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, missed them.

This commit implemented interval arithmetic as class IV in sympy.core.sets Now
sympy can:
- add and subtract intervals with intervals and Real Numbers
- Multiply and divide intervals with intervals and Real Numbers
- exponentiate an interval to some positive integer power
- substitute an expression with interval and evaluate the resulting interval
@hargup
Copy link
Contributor Author

hargup commented Dec 28, 2013

And maybe the current Set class is wrong. Probably, it should be like builtin set, and not to overload add as ?> union, for example. See this:
http://docs.python.org/3.4/library/stdtypes.html#set-types-set-frozenset

I removed the __add__, __sub__ and __mul__ procedure from the Set class and ran the test suit to see what breaks. Apart from the obvious failure in test for sets module a few other module uses the overloaded __add__ as union and __mul__ as cartesian product on Interval. Here are the test results.

https://gist.github.com/hargup/8161971
So, changing the current behavior of Interval might not turn to be an easy task, even if we want to do it.

@skirpichev
Copy link
Contributor

On Sat, Dec 28, 2013 at 09:58:53AM -0800, Harsh Gupta wrote:

So, changing the current behavior of Interval might not turn to be an easy
task, even if we want to do it.

It was just a suggestion (and maybe it has been already
discussed), I'm not sure we want to do it.

@@ -222,6 +224,16 @@ def eval(cls, arg):
return S.One
elif arg.is_negative:
return cls(-arg)
elif isinstance(arg, IV):
if arg.start > S.Zero:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arg.start.is_positive

and so on...

Added code to handle IV as arguments in elementary functions.
Following functions are now supported:
- exponential: exp, log
- trigonometric: sin, cos, tan, cot
- hyperbolic: sinh, cosh
@skirpichev
Copy link
Contributor

And anyhow, unless you're a mathematician, I think it's pretty hard to remember that | is union (I always have to stop for a second and think, "OK, union, that's 'or', so |").

It's all about python compatibility. If Set is a set of sympy's objects - maybe the same (as for builtin set container) interface makes sense.

I have move the IV class back to sets.py

If we keep separate IV and Interval - you should choose a good name for the first.

@hargup
Copy link
Contributor Author

hargup commented Dec 30, 2013

If we keep separate IV and Interval - you should choose a good name for the first.

Can you suggest any?

@skirpichev
Copy link
Contributor

Can you suggest any?

Sorry :(

@asmeurer
Copy link
Member

I agree we should keep the same interface, but we should also keep + for union.

@@ -257,6 +257,12 @@ def test_sympy__core__function__WildFunction():
assert _test_args(WildFunction('f'))


@XFAIL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails because two parameters for IV are left_open and right_open which are Python booleans, hence are not an instance of Basic.

@asmeurer
Copy link
Member

Classes like this really show the need for a good dispatching system. One should be able to write this without touching any other file.

@mrocklin
Copy link
Member

Sorry I'm late to the discussion. I think that this class should inherit from Expr and contain-but-not-inherit-from Interval. I would suggest that it does not inherit from both simultaneously.

Also, I think it should contain a Set, not an Interval How much of this logic is generalizable? Consider the following example

X = SetExpr(FiniteSet(1, 2))
Y = SetExpr(FiniteSet(2, 3))

# Evaluates to true?
X + Y == SetExpr(FiniteSet(3, 4, 5))

How far can we generalize the notion of an expression that carries around a set?

SymPy.stats does this to some extent. Each symbol carries around both a set and a probability distribution on that set. Expressions containing multiple random symbols are projections onto cartesian products of sets. This Set-Expr issue should be a much easier problem. It may be that Intervals are easier to work with and deserve special attention. Some sort of simplification scheme might be helpful here.

@asmeurer
Copy link
Member

+1 to Matthew's comments.

@mrocklin
Copy link
Member

I should also note that I'm totally in favor of work in this direction. I think that an Expr frontend to sets and particularly to intervals would be a fantastic contribution!

@mrocklin
Copy link
Member

For scalar functions on sets I think that the natural thing to do is to rely on ImageSet. In general something like the following should work for many interactions

f(SetExpr(a_set)) -> SetExpr(imageset(f, a_set))

In this way we rely on a general computational piece of sets to do much of the work for us. I expect this to expose errors in ImageSet. That's good.

This doesn't handle set-set interactions though.

@mrocklin mrocklin mentioned this pull request Jan 1, 2014
4 tasks
@mrocklin
Copy link
Member

mrocklin commented Jan 1, 2014

I set up a draft of my set expression idea in #2721

@hargup
Copy link
Contributor Author

hargup commented Jan 1, 2014

I should also note that I'm totally in favor of work in this direction. I think that an Expr frontend to sets and particularly to intervals would be a fantastic contribution!

Thanks a lot your words are encouraging.

Also, I think it should contain a Set, not an Interval.

That will be definitely better. The need to implement operations on Interval arose to handle cases like sin(x) --> oo and tan(x) --> oo in limits and other expressions [1]. But using only Intervals won't be sufficient. For example limit(sec(x), x, oo) should return (-oo, -1]U[1, oo) it won't be possible to get it by using only Intervals, we will have to use Set as Expressions.

@hargup
Copy link
Contributor Author

hargup commented Jan 1, 2014

For scalar functions on sets I think that the natural thing to do is to rely on ImageSet

I was not of aware of ImageSet, thanks for pointing out. I went through its source and what I understood is to check if something is in ImageSet solves the given function minus the parameter and check if it is in the base set. In a sense ImageSet doesn't really know what its content are. Hence, it doesn't play well when Intervals are given as base_set.

In [14]: sqrs = ImageSet(Lambda(x, x**2), Interval(0, 2))

In [15]: Interval(-10, 10).intersect(sqrs)
Out[15]: Intersection([-10, 10], ImageSet(Lambda(x, x**2), [0, 2])) 

I think ImageSet should return the resulting Set when it can do so.
At least for some cases where we can solve the derivative of the function we can compute the resulting ImageSet for an Interval by finding out the local Maximas and Minimas of the function in the base Interval. And for FiniteSet we can simply compute and store the resulting value for each member.

@mrocklin
Copy link
Member

mrocklin commented Jan 1, 2014

Sorry, I should have suggested that you use imageset rather than ImageSet.

imageset relies on _eval_imageset implemented in various Set classes. That is where much of the actual logic exists.

In [13]: ImageSet(Lambda(x, x**2), Interval(0, 2))
Out[13]: 
⎧ 2             ⎫
⎨x  | x ∊ [0, 2]⎬
⎩               ⎭

In [14]: imageset(Lambda(x, x**2), Interval(0, 2))
Out[14]: [0, 4]

@hargup
Copy link
Contributor Author

hargup commented Jan 1, 2014

imageset is also not doing the right thing. As of now it works fine only if the given function is monotonic on both positive reals and negative reals. For example it fails here:

imageset(Lambda(x, (x-2)**2), Interval(1, 3))
Out[14]: {1}

@mrocklin
Copy link
Member

mrocklin commented Jan 1, 2014

Good catch. Looks like Interval._eval_imageset could use some work.

@hargup
Copy link
Contributor Author

hargup commented Jan 2, 2014

I have implemented an initial fix for Interval._eval_imageset in #2723

@hargup
Copy link
Contributor Author

hargup commented Jan 4, 2014

I think this PR is no longer needed when Matthew is working on SetExpr and I'm working on imagesets

@hargup hargup closed this Jan 4, 2014
@mrocklin
Copy link
Member

mrocklin commented Jan 5, 2014

While I do think that SetExpr is the way to go it's not yet at the level of maturity of this pull request. In particular we should probably do automatic evaluation somewhere as the IV class does rather than rely on setexpr.simplify

@skirpichev
Copy link
Contributor

On Wed, Jan 01, 2014 at 08:07:43AM -0800, Harsh Gupta wrote:

That will be definitely better. The need to implement operations on
Interval arose to handle cases like sin(x) --> oo and tan(x) --> oo in
limits and other expressions [[1]1]. But using only Intervals won't be
sufficient. For example limit(sec(x), x, oo) should return (-oo, -1]U[1,
oo) it won't be possible to get it by only using Intervals, we will have
to use Set as Expressions.

@hargup, please take look on
https://github.com/sympy/sympy/wiki/Infinities-and-Singularities

gxyd pushed a commit to gxyd/sympy that referenced this pull request Dec 18, 2015
Limits no longer a subclass of `Interval`

S.false removed from Basic call for Limits, docstring for relationals

repetitive test removed and Pow approach changed for Limits

comments for future work added

`Limits` -> `AccumulationBounds` (alias `AccumBounds`)
also code from sympy#2682 taken from Harsha's PR.

fixes sympy#5299 sympy#9934

changes `_print_Limits` -> `_print_AccumulationBouds`
@gxyd gxyd mentioned this pull request Oct 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants