Spaces:
Sleeping
Sleeping
from typing import Tuple as tTuple | |
from sympy.core.basic import Basic | |
from sympy.core.expr import Expr | |
from sympy.core import Add, S | |
from sympy.core.evalf import get_integer_part, PrecisionExhausted | |
from sympy.core.function import Function | |
from sympy.core.logic import fuzzy_or | |
from sympy.core.numbers import Integer, int_valued | |
from sympy.core.relational import Gt, Lt, Ge, Le, Relational, is_eq | |
from sympy.core.sympify import _sympify | |
from sympy.functions.elementary.complexes import im, re | |
from sympy.multipledispatch import dispatch | |
############################################################################### | |
######################### FLOOR and CEILING FUNCTIONS ######################### | |
############################################################################### | |
class RoundFunction(Function): | |
"""Abstract base class for rounding functions.""" | |
args: tTuple[Expr] | |
def eval(cls, arg): | |
v = cls._eval_number(arg) | |
if v is not None: | |
return v | |
if arg.is_integer or arg.is_finite is False: | |
return arg | |
if arg.is_imaginary or (S.ImaginaryUnit*arg).is_real: | |
i = im(arg) | |
if not i.has(S.ImaginaryUnit): | |
return cls(i)*S.ImaginaryUnit | |
return cls(arg, evaluate=False) | |
# Integral, numerical, symbolic part | |
ipart = npart = spart = S.Zero | |
# Extract integral (or complex integral) terms | |
intof = lambda x: int(x) if int_valued(x) else ( | |
x if x.is_integer else None) | |
for t in Add.make_args(arg): | |
if t.is_imaginary and (i := intof(im(t))) is not None: | |
ipart += i*S.ImaginaryUnit | |
elif (i := intof(t)) is not None: | |
ipart += i | |
elif t.is_number: | |
npart += t | |
else: | |
spart += t | |
if not (npart or spart): | |
return ipart | |
# Evaluate npart numerically if independent of spart | |
if npart and ( | |
not spart or | |
npart.is_real and (spart.is_imaginary or (S.ImaginaryUnit*spart).is_real) or | |
npart.is_imaginary and spart.is_real): | |
try: | |
r, i = get_integer_part( | |
npart, cls._dir, {}, return_ints=True) | |
ipart += Integer(r) + Integer(i)*S.ImaginaryUnit | |
npart = S.Zero | |
except (PrecisionExhausted, NotImplementedError): | |
pass | |
spart += npart | |
if not spart: | |
return ipart | |
elif spart.is_imaginary or (S.ImaginaryUnit*spart).is_real: | |
return ipart + cls(im(spart), evaluate=False)*S.ImaginaryUnit | |
elif isinstance(spart, (floor, ceiling)): | |
return ipart + spart | |
else: | |
return ipart + cls(spart, evaluate=False) | |
def _eval_number(cls, arg): | |
raise NotImplementedError() | |
def _eval_is_finite(self): | |
return self.args[0].is_finite | |
def _eval_is_real(self): | |
return self.args[0].is_real | |
def _eval_is_integer(self): | |
return self.args[0].is_real | |
class floor(RoundFunction): | |
""" | |
Floor is a univariate function which returns the largest integer | |
value not greater than its argument. This implementation | |
generalizes floor to complex numbers by taking the floor of the | |
real and imaginary parts separately. | |
Examples | |
======== | |
>>> from sympy import floor, E, I, S, Float, Rational | |
>>> floor(17) | |
17 | |
>>> floor(Rational(23, 10)) | |
2 | |
>>> floor(2*E) | |
5 | |
>>> floor(-Float(0.567)) | |
-1 | |
>>> floor(-I/2) | |
-I | |
>>> floor(S(5)/2 + 5*I/2) | |
2 + 2*I | |
See Also | |
======== | |
sympy.functions.elementary.integers.ceiling | |
References | |
========== | |
.. [1] "Concrete mathematics" by Graham, pp. 87 | |
.. [2] https://mathworld.wolfram.com/FloorFunction.html | |
""" | |
_dir = -1 | |
def _eval_number(cls, arg): | |
if arg.is_Number: | |
return arg.floor() | |
elif any(isinstance(i, j) | |
for i in (arg, -arg) for j in (floor, ceiling)): | |
return arg | |
if arg.is_NumberSymbol: | |
return arg.approximation_interval(Integer)[0] | |
def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
from sympy.calculus.accumulationbounds import AccumBounds | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0) | |
r = self.subs(x, 0) | |
if arg0 is S.NaN or isinstance(arg0, AccumBounds): | |
arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') | |
r = floor(arg0) | |
if arg0.is_finite: | |
if arg0 == r: | |
ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) | |
if ndir.is_negative: | |
return r - 1 | |
elif ndir.is_positive: | |
return r | |
else: | |
raise NotImplementedError("Not sure of sign of %s" % ndir) | |
else: | |
return r | |
return arg.as_leading_term(x, logx=logx, cdir=cdir) | |
def _eval_nseries(self, x, n, logx, cdir=0): | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0) | |
r = self.subs(x, 0) | |
if arg0 is S.NaN: | |
arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') | |
r = floor(arg0) | |
if arg0.is_infinite: | |
from sympy.calculus.accumulationbounds import AccumBounds | |
from sympy.series.order import Order | |
s = arg._eval_nseries(x, n, logx, cdir) | |
o = Order(1, (x, 0)) if n <= 0 else AccumBounds(-1, 0) | |
return s + o | |
if arg0 == r: | |
ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) | |
if ndir.is_negative: | |
return r - 1 | |
elif ndir.is_positive: | |
return r | |
else: | |
raise NotImplementedError("Not sure of sign of %s" % ndir) | |
else: | |
return r | |
def _eval_is_negative(self): | |
return self.args[0].is_negative | |
def _eval_is_nonnegative(self): | |
return self.args[0].is_nonnegative | |
def _eval_rewrite_as_ceiling(self, arg, **kwargs): | |
return -ceiling(-arg) | |
def _eval_rewrite_as_frac(self, arg, **kwargs): | |
return arg - frac(arg) | |
def __le__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] < other + 1 | |
if other.is_number and other.is_real: | |
return self.args[0] < ceiling(other) | |
if self.args[0] == other and other.is_real: | |
return S.true | |
if other is S.Infinity and self.is_finite: | |
return S.true | |
return Le(self, other, evaluate=False) | |
def __ge__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] >= other | |
if other.is_number and other.is_real: | |
return self.args[0] >= ceiling(other) | |
if self.args[0] == other and other.is_real: | |
return S.false | |
if other is S.NegativeInfinity and self.is_finite: | |
return S.true | |
return Ge(self, other, evaluate=False) | |
def __gt__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] >= other + 1 | |
if other.is_number and other.is_real: | |
return self.args[0] >= ceiling(other) | |
if self.args[0] == other and other.is_real: | |
return S.false | |
if other is S.NegativeInfinity and self.is_finite: | |
return S.true | |
return Gt(self, other, evaluate=False) | |
def __lt__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] < other | |
if other.is_number and other.is_real: | |
return self.args[0] < ceiling(other) | |
if self.args[0] == other and other.is_real: | |
return S.false | |
if other is S.Infinity and self.is_finite: | |
return S.true | |
return Lt(self, other, evaluate=False) | |
def _eval_is_eq(lhs, rhs): # noqa:F811 | |
return is_eq(lhs.rewrite(ceiling), rhs) or \ | |
is_eq(lhs.rewrite(frac),rhs) | |
class ceiling(RoundFunction): | |
""" | |
Ceiling is a univariate function which returns the smallest integer | |
value not less than its argument. This implementation | |
generalizes ceiling to complex numbers by taking the ceiling of the | |
real and imaginary parts separately. | |
Examples | |
======== | |
>>> from sympy import ceiling, E, I, S, Float, Rational | |
>>> ceiling(17) | |
17 | |
>>> ceiling(Rational(23, 10)) | |
3 | |
>>> ceiling(2*E) | |
6 | |
>>> ceiling(-Float(0.567)) | |
0 | |
>>> ceiling(I/2) | |
I | |
>>> ceiling(S(5)/2 + 5*I/2) | |
3 + 3*I | |
See Also | |
======== | |
sympy.functions.elementary.integers.floor | |
References | |
========== | |
.. [1] "Concrete mathematics" by Graham, pp. 87 | |
.. [2] https://mathworld.wolfram.com/CeilingFunction.html | |
""" | |
_dir = 1 | |
def _eval_number(cls, arg): | |
if arg.is_Number: | |
return arg.ceiling() | |
elif any(isinstance(i, j) | |
for i in (arg, -arg) for j in (floor, ceiling)): | |
return arg | |
if arg.is_NumberSymbol: | |
return arg.approximation_interval(Integer)[1] | |
def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
from sympy.calculus.accumulationbounds import AccumBounds | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0) | |
r = self.subs(x, 0) | |
if arg0 is S.NaN or isinstance(arg0, AccumBounds): | |
arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') | |
r = ceiling(arg0) | |
if arg0.is_finite: | |
if arg0 == r: | |
ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) | |
if ndir.is_negative: | |
return r | |
elif ndir.is_positive: | |
return r + 1 | |
else: | |
raise NotImplementedError("Not sure of sign of %s" % ndir) | |
else: | |
return r | |
return arg.as_leading_term(x, logx=logx, cdir=cdir) | |
def _eval_nseries(self, x, n, logx, cdir=0): | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0) | |
r = self.subs(x, 0) | |
if arg0 is S.NaN: | |
arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') | |
r = ceiling(arg0) | |
if arg0.is_infinite: | |
from sympy.calculus.accumulationbounds import AccumBounds | |
from sympy.series.order import Order | |
s = arg._eval_nseries(x, n, logx, cdir) | |
o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1) | |
return s + o | |
if arg0 == r: | |
ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) | |
if ndir.is_negative: | |
return r | |
elif ndir.is_positive: | |
return r + 1 | |
else: | |
raise NotImplementedError("Not sure of sign of %s" % ndir) | |
else: | |
return r | |
def _eval_rewrite_as_floor(self, arg, **kwargs): | |
return -floor(-arg) | |
def _eval_rewrite_as_frac(self, arg, **kwargs): | |
return arg + frac(-arg) | |
def _eval_is_positive(self): | |
return self.args[0].is_positive | |
def _eval_is_nonpositive(self): | |
return self.args[0].is_nonpositive | |
def __lt__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] <= other - 1 | |
if other.is_number and other.is_real: | |
return self.args[0] <= floor(other) | |
if self.args[0] == other and other.is_real: | |
return S.false | |
if other is S.Infinity and self.is_finite: | |
return S.true | |
return Lt(self, other, evaluate=False) | |
def __gt__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] > other | |
if other.is_number and other.is_real: | |
return self.args[0] > floor(other) | |
if self.args[0] == other and other.is_real: | |
return S.false | |
if other is S.NegativeInfinity and self.is_finite: | |
return S.true | |
return Gt(self, other, evaluate=False) | |
def __ge__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] > other - 1 | |
if other.is_number and other.is_real: | |
return self.args[0] > floor(other) | |
if self.args[0] == other and other.is_real: | |
return S.true | |
if other is S.NegativeInfinity and self.is_finite: | |
return S.true | |
return Ge(self, other, evaluate=False) | |
def __le__(self, other): | |
other = S(other) | |
if self.args[0].is_real: | |
if other.is_integer: | |
return self.args[0] <= other | |
if other.is_number and other.is_real: | |
return self.args[0] <= floor(other) | |
if self.args[0] == other and other.is_real: | |
return S.false | |
if other is S.Infinity and self.is_finite: | |
return S.true | |
return Le(self, other, evaluate=False) | |
# type:ignore | |
def _eval_is_eq(lhs, rhs): # noqa:F811 | |
return is_eq(lhs.rewrite(floor), rhs) or is_eq(lhs.rewrite(frac),rhs) | |
class frac(Function): | |
r"""Represents the fractional part of x | |
For real numbers it is defined [1]_ as | |
.. math:: | |
x - \left\lfloor{x}\right\rfloor | |
Examples | |
======== | |
>>> from sympy import Symbol, frac, Rational, floor, I | |
>>> frac(Rational(4, 3)) | |
1/3 | |
>>> frac(-Rational(4, 3)) | |
2/3 | |
returns zero for integer arguments | |
>>> n = Symbol('n', integer=True) | |
>>> frac(n) | |
0 | |
rewrite as floor | |
>>> x = Symbol('x') | |
>>> frac(x).rewrite(floor) | |
x - floor(x) | |
for complex arguments | |
>>> r = Symbol('r', real=True) | |
>>> t = Symbol('t', real=True) | |
>>> frac(t + I*r) | |
I*frac(r) + frac(t) | |
See Also | |
======== | |
sympy.functions.elementary.integers.floor | |
sympy.functions.elementary.integers.ceiling | |
References | |
=========== | |
.. [1] https://en.wikipedia.org/wiki/Fractional_part | |
.. [2] https://mathworld.wolfram.com/FractionalPart.html | |
""" | |
def eval(cls, arg): | |
from sympy.calculus.accumulationbounds import AccumBounds | |
def _eval(arg): | |
if arg in (S.Infinity, S.NegativeInfinity): | |
return AccumBounds(0, 1) | |
if arg.is_integer: | |
return S.Zero | |
if arg.is_number: | |
if arg is S.NaN: | |
return S.NaN | |
elif arg is S.ComplexInfinity: | |
return S.NaN | |
else: | |
return arg - floor(arg) | |
return cls(arg, evaluate=False) | |
real, imag = S.Zero, S.Zero | |
for t in Add.make_args(arg): | |
# Two checks are needed for complex arguments | |
# see issue-7649 for details | |
if t.is_imaginary or (S.ImaginaryUnit*t).is_real: | |
i = im(t) | |
if not i.has(S.ImaginaryUnit): | |
imag += i | |
else: | |
real += t | |
else: | |
real += t | |
real = _eval(real) | |
imag = _eval(imag) | |
return real + S.ImaginaryUnit*imag | |
def _eval_rewrite_as_floor(self, arg, **kwargs): | |
return arg - floor(arg) | |
def _eval_rewrite_as_ceiling(self, arg, **kwargs): | |
return arg + ceiling(-arg) | |
def _eval_is_finite(self): | |
return True | |
def _eval_is_real(self): | |
return self.args[0].is_extended_real | |
def _eval_is_imaginary(self): | |
return self.args[0].is_imaginary | |
def _eval_is_integer(self): | |
return self.args[0].is_integer | |
def _eval_is_zero(self): | |
return fuzzy_or([self.args[0].is_zero, self.args[0].is_integer]) | |
def _eval_is_negative(self): | |
return False | |
def __ge__(self, other): | |
if self.is_extended_real: | |
other = _sympify(other) | |
# Check if other <= 0 | |
if other.is_extended_nonpositive: | |
return S.true | |
# Check if other >= 1 | |
res = self._value_one_or_more(other) | |
if res is not None: | |
return not(res) | |
return Ge(self, other, evaluate=False) | |
def __gt__(self, other): | |
if self.is_extended_real: | |
other = _sympify(other) | |
# Check if other < 0 | |
res = self._value_one_or_more(other) | |
if res is not None: | |
return not(res) | |
# Check if other >= 1 | |
if other.is_extended_negative: | |
return S.true | |
return Gt(self, other, evaluate=False) | |
def __le__(self, other): | |
if self.is_extended_real: | |
other = _sympify(other) | |
# Check if other < 0 | |
if other.is_extended_negative: | |
return S.false | |
# Check if other >= 1 | |
res = self._value_one_or_more(other) | |
if res is not None: | |
return res | |
return Le(self, other, evaluate=False) | |
def __lt__(self, other): | |
if self.is_extended_real: | |
other = _sympify(other) | |
# Check if other <= 0 | |
if other.is_extended_nonpositive: | |
return S.false | |
# Check if other >= 1 | |
res = self._value_one_or_more(other) | |
if res is not None: | |
return res | |
return Lt(self, other, evaluate=False) | |
def _value_one_or_more(self, other): | |
if other.is_extended_real: | |
if other.is_number: | |
res = other >= 1 | |
if res and not isinstance(res, Relational): | |
return S.true | |
if other.is_integer and other.is_positive: | |
return S.true | |
def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
from sympy.calculus.accumulationbounds import AccumBounds | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0) | |
r = self.subs(x, 0) | |
if arg0.is_finite: | |
if r.is_zero: | |
ndir = arg.dir(x, cdir=cdir) | |
if ndir.is_negative: | |
return S.One | |
return (arg - arg0).as_leading_term(x, logx=logx, cdir=cdir) | |
else: | |
return r | |
elif arg0 in (S.ComplexInfinity, S.Infinity, S.NegativeInfinity): | |
return AccumBounds(0, 1) | |
return arg.as_leading_term(x, logx=logx, cdir=cdir) | |
def _eval_nseries(self, x, n, logx, cdir=0): | |
from sympy.series.order import Order | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0) | |
r = self.subs(x, 0) | |
if arg0.is_infinite: | |
from sympy.calculus.accumulationbounds import AccumBounds | |
o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1) + Order(x**n, (x, 0)) | |
return o | |
else: | |
res = (arg - arg0)._eval_nseries(x, n, logx=logx, cdir=cdir) | |
if r.is_zero: | |
ndir = arg.dir(x, cdir=cdir) | |
res += S.One if ndir.is_negative else S.Zero | |
else: | |
res += r | |
return res | |
# type:ignore | |
def _eval_is_eq(lhs, rhs): # noqa:F811 | |
if (lhs.rewrite(floor) == rhs) or \ | |
(lhs.rewrite(ceiling) == rhs): | |
return True | |
# Check if other < 0 | |
if rhs.is_extended_negative: | |
return False | |
# Check if other >= 1 | |
res = lhs._value_one_or_more(rhs) | |
if res is not None: | |
return False | |