|
from sympy.core import Add, Mul, Pow, S |
|
from sympy.core.basic import Basic |
|
from sympy.core.expr import Expr |
|
from sympy.core.numbers import _sympifyit, oo, zoo |
|
from sympy.core.relational import is_le, is_lt, is_ge, is_gt |
|
from sympy.core.sympify import _sympify |
|
from sympy.functions.elementary.miscellaneous import Min, Max |
|
from sympy.logic.boolalg import And |
|
from sympy.multipledispatch import dispatch |
|
from sympy.series.order import Order |
|
from sympy.sets.sets import FiniteSet |
|
|
|
|
|
class AccumulationBounds(Expr): |
|
r"""An accumulation bounds. |
|
|
|
# Note AccumulationBounds has an alias: AccumBounds |
|
|
|
AccumulationBounds represent an interval `[a, b]`, which is always closed |
|
at the ends. Here `a` and `b` can be any value from extended real numbers. |
|
|
|
The intended meaning of AccummulationBounds is to give an approximate |
|
location of the accumulation points of a real function at a limit point. |
|
|
|
Let `a` and `b` be reals such that `a \le b`. |
|
|
|
`\left\langle a, b\right\rangle = \{x \in \mathbb{R} \mid a \le x \le b\}` |
|
|
|
`\left\langle -\infty, b\right\rangle = \{x \in \mathbb{R} \mid x \le b\} \cup \{-\infty, \infty\}` |
|
|
|
`\left\langle a, \infty \right\rangle = \{x \in \mathbb{R} \mid a \le x\} \cup \{-\infty, \infty\}` |
|
|
|
`\left\langle -\infty, \infty \right\rangle = \mathbb{R} \cup \{-\infty, \infty\}` |
|
|
|
``oo`` and ``-oo`` are added to the second and third definition respectively, |
|
since if either ``-oo`` or ``oo`` is an argument, then the other one should |
|
be included (though not as an end point). This is forced, since we have, |
|
for example, ``1/AccumBounds(0, 1) = AccumBounds(1, oo)``, and the limit at |
|
`0` is not one-sided. As `x` tends to `0-`, then `1/x \rightarrow -\infty`, so `-\infty` |
|
should be interpreted as belonging to ``AccumBounds(1, oo)`` though it need |
|
not appear explicitly. |
|
|
|
In many cases it suffices to know that the limit set is bounded. |
|
However, in some other cases more exact information could be useful. |
|
For example, all accumulation values of `\cos(x) + 1` are non-negative. |
|
(``AccumBounds(-1, 1) + 1 = AccumBounds(0, 2)``) |
|
|
|
A AccumulationBounds object is defined to be real AccumulationBounds, |
|
if its end points are finite reals. |
|
|
|
Let `X`, `Y` be real AccumulationBounds, then their sum, difference, |
|
product are defined to be the following sets: |
|
|
|
`X + Y = \{ x+y \mid x \in X \cap y \in Y\}` |
|
|
|
`X - Y = \{ x-y \mid x \in X \cap y \in Y\}` |
|
|
|
`X \times Y = \{ x \times y \mid x \in X \cap y \in Y\}` |
|
|
|
When an AccumBounds is raised to a negative power, if 0 is contained |
|
between the bounds then an infinite range is returned, otherwise if an |
|
endpoint is 0 then a semi-infinite range with consistent sign will be returned. |
|
|
|
AccumBounds in expressions behave a lot like Intervals but the |
|
semantics are not necessarily the same. Division (or exponentiation |
|
to a negative integer power) could be handled with *intervals* by |
|
returning a union of the results obtained after splitting the |
|
bounds between negatives and positives, but that is not done with |
|
AccumBounds. In addition, bounds are assumed to be independent of |
|
each other; if the same bound is used in more than one place in an |
|
expression, the result may not be the supremum or infimum of the |
|
expression (see below). Finally, when a boundary is ``1``, |
|
exponentiation to the power of ``oo`` yields ``oo``, neither |
|
``1`` nor ``nan``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds, sin, exp, log, pi, E, S, oo |
|
>>> from sympy.abc import x |
|
|
|
>>> AccumBounds(0, 1) + AccumBounds(1, 2) |
|
AccumBounds(1, 3) |
|
|
|
>>> AccumBounds(0, 1) - AccumBounds(0, 2) |
|
AccumBounds(-2, 1) |
|
|
|
>>> AccumBounds(-2, 3)*AccumBounds(-1, 1) |
|
AccumBounds(-3, 3) |
|
|
|
>>> AccumBounds(1, 2)*AccumBounds(3, 5) |
|
AccumBounds(3, 10) |
|
|
|
The exponentiation of AccumulationBounds is defined |
|
as follows: |
|
|
|
If 0 does not belong to `X` or `n > 0` then |
|
|
|
`X^n = \{ x^n \mid x \in X\}` |
|
|
|
>>> AccumBounds(1, 4)**(S(1)/2) |
|
AccumBounds(1, 2) |
|
|
|
otherwise, an infinite or semi-infinite result is obtained: |
|
|
|
>>> 1/AccumBounds(-1, 1) |
|
AccumBounds(-oo, oo) |
|
>>> 1/AccumBounds(0, 2) |
|
AccumBounds(1/2, oo) |
|
>>> 1/AccumBounds(-oo, 0) |
|
AccumBounds(-oo, 0) |
|
|
|
A boundary of 1 will always generate all nonnegatives: |
|
|
|
>>> AccumBounds(1, 2)**oo |
|
AccumBounds(0, oo) |
|
>>> AccumBounds(0, 1)**oo |
|
AccumBounds(0, oo) |
|
|
|
If the exponent is itself an AccumulationBounds or is not an |
|
integer then unevaluated results will be returned unless the base |
|
values are positive: |
|
|
|
>>> AccumBounds(2, 3)**AccumBounds(-1, 2) |
|
AccumBounds(1/3, 9) |
|
>>> AccumBounds(-2, 3)**AccumBounds(-1, 2) |
|
AccumBounds(-2, 3)**AccumBounds(-1, 2) |
|
|
|
>>> AccumBounds(-2, -1)**(S(1)/2) |
|
sqrt(AccumBounds(-2, -1)) |
|
|
|
Note: `\left\langle a, b\right\rangle^2` is not same as `\left\langle a, b\right\rangle \times \left\langle a, b\right\rangle` |
|
|
|
>>> AccumBounds(-1, 1)**2 |
|
AccumBounds(0, 1) |
|
|
|
>>> AccumBounds(1, 3) < 4 |
|
True |
|
|
|
>>> AccumBounds(1, 3) < -1 |
|
False |
|
|
|
Some elementary functions can also take AccumulationBounds as input. |
|
A function `f` evaluated for some real AccumulationBounds `\left\langle a, b \right\rangle` |
|
is defined as `f(\left\langle a, b\right\rangle) = \{ f(x) \mid a \le x \le b \}` |
|
|
|
>>> sin(AccumBounds(pi/6, pi/3)) |
|
AccumBounds(1/2, sqrt(3)/2) |
|
|
|
>>> exp(AccumBounds(0, 1)) |
|
AccumBounds(1, E) |
|
|
|
>>> log(AccumBounds(1, E)) |
|
AccumBounds(0, 1) |
|
|
|
Some symbol in an expression can be substituted for a AccumulationBounds |
|
object. But it does not necessarily evaluate the AccumulationBounds for |
|
that expression. |
|
|
|
The same expression can be evaluated to different values depending upon |
|
the form it is used for substitution since each instance of an |
|
AccumulationBounds is considered independent. For example: |
|
|
|
>>> (x**2 + 2*x + 1).subs(x, AccumBounds(-1, 1)) |
|
AccumBounds(-1, 4) |
|
|
|
>>> ((x + 1)**2).subs(x, AccumBounds(-1, 1)) |
|
AccumBounds(0, 4) |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Interval_arithmetic |
|
|
|
.. [2] https://fab.cba.mit.edu/classes/S62.12/docs/Hickey_interval.pdf |
|
|
|
Notes |
|
===== |
|
|
|
Do not use ``AccumulationBounds`` for floating point interval arithmetic |
|
calculations, use ``mpmath.iv`` instead. |
|
""" |
|
|
|
is_extended_real = True |
|
is_number = False |
|
|
|
def __new__(cls, min, max) -> Expr: |
|
|
|
min = _sympify(min) |
|
max = _sympify(max) |
|
|
|
|
|
if not min.is_extended_real or not max.is_extended_real: |
|
raise ValueError("Only real AccumulationBounds are supported") |
|
|
|
if max == min: |
|
return max |
|
|
|
|
|
if max.is_number and min.is_number: |
|
bad = max.is_comparable and min.is_comparable and max < min |
|
else: |
|
bad = (max - min).is_extended_negative |
|
if bad: |
|
raise ValueError( |
|
"Lower limit should be smaller than upper limit") |
|
|
|
return Basic.__new__(cls, min, max) |
|
|
|
|
|
_op_priority = 11.0 |
|
|
|
def _eval_is_real(self): |
|
if self.min.is_real and self.max.is_real: |
|
return True |
|
|
|
@property |
|
def min(self): |
|
""" |
|
Returns the minimum possible value attained by AccumulationBounds |
|
object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds |
|
>>> AccumBounds(1, 3).min |
|
1 |
|
|
|
""" |
|
return self.args[0] |
|
|
|
@property |
|
def max(self): |
|
""" |
|
Returns the maximum possible value attained by AccumulationBounds |
|
object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds |
|
>>> AccumBounds(1, 3).max |
|
3 |
|
|
|
""" |
|
return self.args[1] |
|
|
|
@property |
|
def delta(self): |
|
""" |
|
Returns the difference of maximum possible value attained by |
|
AccumulationBounds object and minimum possible value attained |
|
by AccumulationBounds object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds |
|
>>> AccumBounds(1, 3).delta |
|
2 |
|
|
|
""" |
|
return self.max - self.min |
|
|
|
@property |
|
def mid(self): |
|
""" |
|
Returns the mean of maximum possible value attained by |
|
AccumulationBounds object and minimum possible value |
|
attained by AccumulationBounds object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds |
|
>>> AccumBounds(1, 3).mid |
|
2 |
|
|
|
""" |
|
return (self.min + self.max) / 2 |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def _eval_power(self, other): |
|
return self.__pow__(other) |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __add__(self, other): |
|
if isinstance(other, Expr): |
|
if isinstance(other, AccumBounds): |
|
return AccumBounds( |
|
Add(self.min, other.min), |
|
Add(self.max, other.max)) |
|
if other is S.Infinity and self.min is S.NegativeInfinity or \ |
|
other is S.NegativeInfinity and self.max is S.Infinity: |
|
return AccumBounds(-oo, oo) |
|
elif other.is_extended_real: |
|
if self.min is S.NegativeInfinity and self.max is S.Infinity: |
|
return AccumBounds(-oo, oo) |
|
elif self.min is S.NegativeInfinity: |
|
return AccumBounds(-oo, self.max + other) |
|
elif self.max is S.Infinity: |
|
return AccumBounds(self.min + other, oo) |
|
else: |
|
return AccumBounds(Add(self.min, other), Add(self.max, other)) |
|
return Add(self, other, evaluate=False) |
|
return NotImplemented |
|
|
|
__radd__ = __add__ |
|
|
|
def __neg__(self): |
|
return AccumBounds(-self.max, -self.min) |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __sub__(self, other): |
|
if isinstance(other, Expr): |
|
if isinstance(other, AccumBounds): |
|
return AccumBounds( |
|
Add(self.min, -other.max), |
|
Add(self.max, -other.min)) |
|
if other is S.NegativeInfinity and self.min is S.NegativeInfinity or \ |
|
other is S.Infinity and self.max is S.Infinity: |
|
return AccumBounds(-oo, oo) |
|
elif other.is_extended_real: |
|
if self.min is S.NegativeInfinity and self.max is S.Infinity: |
|
return AccumBounds(-oo, oo) |
|
elif self.min is S.NegativeInfinity: |
|
return AccumBounds(-oo, self.max - other) |
|
elif self.max is S.Infinity: |
|
return AccumBounds(self.min - other, oo) |
|
else: |
|
return AccumBounds( |
|
Add(self.min, -other), |
|
Add(self.max, -other)) |
|
return Add(self, -other, evaluate=False) |
|
return NotImplemented |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __rsub__(self, other): |
|
return self.__neg__() + other |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __mul__(self, other): |
|
if self.args == (-oo, oo): |
|
return self |
|
if isinstance(other, Expr): |
|
if isinstance(other, AccumBounds): |
|
if other.args == (-oo, oo): |
|
return other |
|
v = set() |
|
for a in self.args: |
|
vi = other*a |
|
v.update(vi.args or (vi,)) |
|
return AccumBounds(Min(*v), Max(*v)) |
|
if other is S.Infinity: |
|
if self.min.is_zero: |
|
return AccumBounds(0, oo) |
|
if self.max.is_zero: |
|
return AccumBounds(-oo, 0) |
|
if other is S.NegativeInfinity: |
|
if self.min.is_zero: |
|
return AccumBounds(-oo, 0) |
|
if self.max.is_zero: |
|
return AccumBounds(0, oo) |
|
if other.is_extended_real: |
|
if other.is_zero: |
|
if self.max is S.Infinity: |
|
return AccumBounds(0, oo) |
|
if self.min is S.NegativeInfinity: |
|
return AccumBounds(-oo, 0) |
|
return S.Zero |
|
if other.is_extended_positive: |
|
return AccumBounds( |
|
Mul(self.min, other), |
|
Mul(self.max, other)) |
|
elif other.is_extended_negative: |
|
return AccumBounds( |
|
Mul(self.max, other), |
|
Mul(self.min, other)) |
|
if isinstance(other, Order): |
|
return other |
|
return Mul(self, other, evaluate=False) |
|
return NotImplemented |
|
|
|
__rmul__ = __mul__ |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __truediv__(self, other): |
|
if isinstance(other, Expr): |
|
if isinstance(other, AccumBounds): |
|
if other.min.is_positive or other.max.is_negative: |
|
return self * AccumBounds(1/other.max, 1/other.min) |
|
|
|
if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative and |
|
other.min.is_extended_nonpositive and other.max.is_extended_nonnegative): |
|
if self.min.is_zero and other.min.is_zero: |
|
return AccumBounds(0, oo) |
|
if self.max.is_zero and other.min.is_zero: |
|
return AccumBounds(-oo, 0) |
|
return AccumBounds(-oo, oo) |
|
|
|
if self.max.is_extended_negative: |
|
if other.min.is_extended_negative: |
|
if other.max.is_zero: |
|
return AccumBounds(self.max / other.min, oo) |
|
if other.max.is_extended_positive: |
|
|
|
|
|
|
|
return AccumBounds(-oo, oo) |
|
|
|
if other.min.is_zero and other.max.is_extended_positive: |
|
return AccumBounds(-oo, self.max / other.max) |
|
|
|
if self.min.is_extended_positive: |
|
if other.min.is_extended_negative: |
|
if other.max.is_zero: |
|
return AccumBounds(-oo, self.min / other.min) |
|
if other.max.is_extended_positive: |
|
|
|
|
|
|
|
return AccumBounds(-oo, oo) |
|
|
|
if other.min.is_zero and other.max.is_extended_positive: |
|
return AccumBounds(self.min / other.max, oo) |
|
|
|
elif other.is_extended_real: |
|
if other in (S.Infinity, S.NegativeInfinity): |
|
if self == AccumBounds(-oo, oo): |
|
return AccumBounds(-oo, oo) |
|
if self.max is S.Infinity: |
|
return AccumBounds(Min(0, other), Max(0, other)) |
|
if self.min is S.NegativeInfinity: |
|
return AccumBounds(Min(0, -other), Max(0, -other)) |
|
if other.is_extended_positive: |
|
return AccumBounds(self.min / other, self.max / other) |
|
elif other.is_extended_negative: |
|
return AccumBounds(self.max / other, self.min / other) |
|
if (1 / other) is S.ComplexInfinity: |
|
return Mul(self, 1 / other, evaluate=False) |
|
else: |
|
return Mul(self, 1 / other) |
|
|
|
return NotImplemented |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __rtruediv__(self, other): |
|
if isinstance(other, Expr): |
|
if other.is_extended_real: |
|
if other.is_zero: |
|
return S.Zero |
|
if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative): |
|
if self.min.is_zero: |
|
if other.is_extended_positive: |
|
return AccumBounds(Mul(other, 1 / self.max), oo) |
|
if other.is_extended_negative: |
|
return AccumBounds(-oo, Mul(other, 1 / self.max)) |
|
if self.max.is_zero: |
|
if other.is_extended_positive: |
|
return AccumBounds(-oo, Mul(other, 1 / self.min)) |
|
if other.is_extended_negative: |
|
return AccumBounds(Mul(other, 1 / self.min), oo) |
|
return AccumBounds(-oo, oo) |
|
else: |
|
return AccumBounds(Min(other / self.min, other / self.max), |
|
Max(other / self.min, other / self.max)) |
|
return Mul(other, 1 / self, evaluate=False) |
|
else: |
|
return NotImplemented |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __pow__(self, other): |
|
if isinstance(other, Expr): |
|
if other is S.Infinity: |
|
if self.min.is_extended_nonnegative: |
|
if self.max < 1: |
|
return S.Zero |
|
if self.min > 1: |
|
return S.Infinity |
|
return AccumBounds(0, oo) |
|
elif self.max.is_extended_negative: |
|
if self.min > -1: |
|
return S.Zero |
|
if self.max < -1: |
|
return zoo |
|
return S.NaN |
|
else: |
|
if self.min > -1: |
|
if self.max < 1: |
|
return S.Zero |
|
return AccumBounds(0, oo) |
|
return AccumBounds(-oo, oo) |
|
|
|
if other is S.NegativeInfinity: |
|
return (1/self)**oo |
|
|
|
|
|
if (self.max - self.min).is_nonnegative: |
|
|
|
if self.min.is_nonnegative: |
|
|
|
if other.is_nonnegative: |
|
|
|
return self.func(self.min**other, self.max**other) |
|
|
|
if other.is_zero: |
|
return S.One |
|
|
|
if other.is_Integer or other.is_integer: |
|
if self.min.is_extended_positive: |
|
return AccumBounds( |
|
Min(self.min**other, self.max**other), |
|
Max(self.min**other, self.max**other)) |
|
elif self.max.is_extended_negative: |
|
return AccumBounds( |
|
Min(self.max**other, self.min**other), |
|
Max(self.max**other, self.min**other)) |
|
|
|
if other % 2 == 0: |
|
if other.is_extended_negative: |
|
if self.min.is_zero: |
|
return AccumBounds(self.max**other, oo) |
|
if self.max.is_zero: |
|
return AccumBounds(self.min**other, oo) |
|
return (1/self)**(-other) |
|
return AccumBounds( |
|
S.Zero, Max(self.min**other, self.max**other)) |
|
elif other % 2 == 1: |
|
if other.is_extended_negative: |
|
if self.min.is_zero: |
|
return AccumBounds(self.max**other, oo) |
|
if self.max.is_zero: |
|
return AccumBounds(-oo, self.min**other) |
|
return (1/self)**(-other) |
|
return AccumBounds(self.min**other, self.max**other) |
|
|
|
|
|
|
|
if (other.is_number or other.is_rational) and ( |
|
self.min.is_extended_nonnegative or ( |
|
other.is_extended_nonnegative and |
|
self.min.is_extended_nonnegative)): |
|
num, den = other.as_numer_denom() |
|
if num is S.One: |
|
return AccumBounds(*[i**(1/den) for i in self.args]) |
|
|
|
elif den is not S.One: |
|
return (self**num)**(1/den) |
|
|
|
if isinstance(other, AccumBounds): |
|
if (self.min.is_extended_positive or |
|
self.min.is_extended_nonnegative and |
|
other.min.is_extended_nonnegative): |
|
p = [self**i for i in other.args] |
|
if not any(i.is_Pow for i in p): |
|
a = [j for i in p for j in i.args or (i,)] |
|
try: |
|
return self.func(min(a), max(a)) |
|
except TypeError: |
|
pass |
|
|
|
return Pow(self, other, evaluate=False) |
|
|
|
return NotImplemented |
|
|
|
@_sympifyit('other', NotImplemented) |
|
def __rpow__(self, other): |
|
if other.is_real and other.is_extended_nonnegative and ( |
|
self.max - self.min).is_extended_positive: |
|
if other is S.One: |
|
return S.One |
|
if other.is_extended_positive: |
|
a, b = [other**i for i in self.args] |
|
if min(a, b) != a: |
|
a, b = b, a |
|
return self.func(a, b) |
|
if other.is_zero: |
|
if self.min.is_zero: |
|
return self.func(0, 1) |
|
if self.min.is_extended_positive: |
|
return S.Zero |
|
|
|
return Pow(other, self, evaluate=False) |
|
|
|
def __abs__(self): |
|
if self.max.is_extended_negative: |
|
return self.__neg__() |
|
elif self.min.is_extended_negative: |
|
return AccumBounds(S.Zero, Max(abs(self.min), self.max)) |
|
else: |
|
return self |
|
|
|
|
|
def __contains__(self, other): |
|
""" |
|
Returns ``True`` if other is contained in self, where other |
|
belongs to extended real numbers, ``False`` if not contained, |
|
otherwise TypeError is raised. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds, oo |
|
>>> 1 in AccumBounds(-1, 3) |
|
True |
|
|
|
-oo and oo go together as limits (in AccumulationBounds). |
|
|
|
>>> -oo in AccumBounds(1, oo) |
|
True |
|
|
|
>>> oo in AccumBounds(-oo, 0) |
|
True |
|
|
|
""" |
|
other = _sympify(other) |
|
|
|
if other in (S.Infinity, S.NegativeInfinity): |
|
if self.min is S.NegativeInfinity or self.max is S.Infinity: |
|
return True |
|
return False |
|
|
|
rv = And(self.min <= other, self.max >= other) |
|
if rv not in (True, False): |
|
raise TypeError("input failed to evaluate") |
|
return rv |
|
|
|
def intersection(self, other): |
|
""" |
|
Returns the intersection of 'self' and 'other'. |
|
Here other can be an instance of :py:class:`~.FiniteSet` or AccumulationBounds. |
|
|
|
Parameters |
|
========== |
|
|
|
other : AccumulationBounds |
|
Another AccumulationBounds object with which the intersection |
|
has to be computed. |
|
|
|
Returns |
|
======= |
|
|
|
AccumulationBounds |
|
Intersection of ``self`` and ``other``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds, FiniteSet |
|
>>> AccumBounds(1, 3).intersection(AccumBounds(2, 4)) |
|
AccumBounds(2, 3) |
|
|
|
>>> AccumBounds(1, 3).intersection(AccumBounds(4, 6)) |
|
EmptySet |
|
|
|
>>> AccumBounds(1, 4).intersection(FiniteSet(1, 2, 5)) |
|
{1, 2} |
|
|
|
""" |
|
if not isinstance(other, (AccumBounds, FiniteSet)): |
|
raise TypeError( |
|
"Input must be AccumulationBounds or FiniteSet object") |
|
|
|
if isinstance(other, FiniteSet): |
|
fin_set = S.EmptySet |
|
for i in other: |
|
if i in self: |
|
fin_set = fin_set + FiniteSet(i) |
|
return fin_set |
|
|
|
if self.max < other.min or self.min > other.max: |
|
return S.EmptySet |
|
|
|
if self.min <= other.min: |
|
if self.max <= other.max: |
|
return AccumBounds(other.min, self.max) |
|
if self.max > other.max: |
|
return other |
|
|
|
if other.min <= self.min: |
|
if other.max < self.max: |
|
return AccumBounds(self.min, other.max) |
|
if other.max > self.max: |
|
return self |
|
|
|
def union(self, other): |
|
|
|
|
|
|
|
if not isinstance(other, AccumBounds): |
|
raise TypeError( |
|
"Input must be AccumulationBounds or FiniteSet object") |
|
|
|
if self.min <= other.min and self.max >= other.min: |
|
return AccumBounds(self.min, Max(self.max, other.max)) |
|
|
|
if other.min <= self.min and other.max >= self.min: |
|
return AccumBounds(other.min, Max(self.max, other.max)) |
|
|
|
|
|
@dispatch(AccumulationBounds, AccumulationBounds) |
|
def _eval_is_le(lhs, rhs): |
|
if is_le(lhs.max, rhs.min): |
|
return True |
|
if is_gt(lhs.min, rhs.max): |
|
return False |
|
|
|
|
|
@dispatch(AccumulationBounds, Basic) |
|
def _eval_is_le(lhs, rhs): |
|
|
|
""" |
|
Returns ``True `` if range of values attained by ``lhs`` AccumulationBounds |
|
object is greater than the range of values attained by ``rhs``, |
|
where ``rhs`` may be any value of type AccumulationBounds object or |
|
extended real number value, ``False`` if ``rhs`` satisfies |
|
the same property, else an unevaluated :py:class:`~.Relational`. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds, oo |
|
>>> AccumBounds(1, 3) > AccumBounds(4, oo) |
|
False |
|
>>> AccumBounds(1, 4) > AccumBounds(3, 4) |
|
AccumBounds(1, 4) > AccumBounds(3, 4) |
|
>>> AccumBounds(1, oo) > -1 |
|
True |
|
|
|
""" |
|
if not rhs.is_extended_real: |
|
raise TypeError( |
|
"Invalid comparison of %s %s" % |
|
(type(rhs), rhs)) |
|
elif rhs.is_comparable: |
|
if is_le(lhs.max, rhs): |
|
return True |
|
if is_gt(lhs.min, rhs): |
|
return False |
|
|
|
|
|
@dispatch(AccumulationBounds, AccumulationBounds) |
|
def _eval_is_ge(lhs, rhs): |
|
if is_ge(lhs.min, rhs.max): |
|
return True |
|
if is_lt(lhs.max, rhs.min): |
|
return False |
|
|
|
|
|
@dispatch(AccumulationBounds, Expr) |
|
def _eval_is_ge(lhs, rhs): |
|
""" |
|
Returns ``True`` if range of values attained by ``lhs`` AccumulationBounds |
|
object is less that the range of values attained by ``rhs``, where |
|
other may be any value of type AccumulationBounds object or extended |
|
real number value, ``False`` if ``rhs`` satisfies the same |
|
property, else an unevaluated :py:class:`~.Relational`. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import AccumBounds, oo |
|
>>> AccumBounds(1, 3) >= AccumBounds(4, oo) |
|
False |
|
>>> AccumBounds(1, 4) >= AccumBounds(3, 4) |
|
AccumBounds(1, 4) >= AccumBounds(3, 4) |
|
>>> AccumBounds(1, oo) >= 1 |
|
True |
|
""" |
|
|
|
if not rhs.is_extended_real: |
|
raise TypeError( |
|
"Invalid comparison of %s %s" % |
|
(type(rhs), rhs)) |
|
elif rhs.is_comparable: |
|
if is_ge(lhs.min, rhs): |
|
return True |
|
if is_lt(lhs.max, rhs): |
|
return False |
|
|
|
|
|
@dispatch(Expr, AccumulationBounds) |
|
def _eval_is_ge(lhs, rhs): |
|
if not lhs.is_extended_real: |
|
raise TypeError( |
|
"Invalid comparison of %s %s" % |
|
(type(lhs), lhs)) |
|
elif lhs.is_comparable: |
|
if is_le(rhs.max, lhs): |
|
return True |
|
if is_gt(rhs.min, lhs): |
|
return False |
|
|
|
|
|
@dispatch(AccumulationBounds, AccumulationBounds) |
|
def _eval_is_ge(lhs, rhs): |
|
if is_ge(lhs.min, rhs.max): |
|
return True |
|
if is_lt(lhs.max, rhs.min): |
|
return False |
|
|
|
|
|
AccumBounds = AccumulationBounds |
|
|