| from __future__ import annotations |
|
|
| 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 DefinedFunction |
| from sympy.core.logic import fuzzy_or, fuzzy_and |
| from sympy.core.numbers import Integer, int_valued |
| from sympy.core.relational import Gt, Lt, Ge, Le, Relational, is_eq, is_le, is_lt |
| from sympy.core.sympify import _sympify |
| from sympy.functions.elementary.complexes import im, re |
| from sympy.multipledispatch import dispatch |
|
|
| |
| |
| |
|
|
|
|
| class RoundFunction(DefinedFunction): |
| """Abstract base class for rounding functions.""" |
|
|
| args: tuple[Expr] |
|
|
| @classmethod |
| def eval(cls, arg): |
| if (v := cls._eval_number(arg)) is not None: |
| return v |
| if (v := cls._eval_const_number(arg)) 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) |
|
|
| |
| ipart = npart = spart = S.Zero |
|
|
| |
| 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 |
|
|
| |
| 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) |
|
|
| @classmethod |
| 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 |
|
|
| @classmethod |
| def _eval_number(cls, arg): |
| if arg.is_Number: |
| return arg.floor() |
| if 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] |
|
|
| @classmethod |
| def _eval_const_number(cls, arg): |
| if arg.is_real: |
| if arg.is_zero: |
| return S.Zero |
| if arg.is_positive: |
| num, den = arg.as_numer_denom() |
| s = den.is_negative |
| if s is None: |
| return None |
| if s: |
| num, den = -num, -den |
| |
| if is_lt(num, den): |
| return S.Zero |
| |
| if fuzzy_and([is_le(den, num), is_lt(num, 2*den)]): |
| return S.One |
| if arg.is_negative: |
| num, den = arg.as_numer_denom() |
| s = den.is_negative |
| if s is None: |
| return None |
| if s: |
| num, den = -num, -den |
| |
| if is_le(-den, num): |
| return S.NegativeOne |
| |
| if fuzzy_and([is_le(-2*den, num), is_lt(num, -den)]): |
| return Integer(-2) |
|
|
| def _eval_as_leading_term(self, x, logx, cdir): |
| 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 and other.is_noninteger: |
| 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 and other.is_noninteger: |
| return S.true |
| if other is S.Infinity and self.is_finite: |
| return S.true |
|
|
| return Lt(self, other, evaluate=False) |
|
|
|
|
| @dispatch(floor, Expr) |
| def _eval_is_eq(lhs, rhs): |
| 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 |
|
|
| @classmethod |
| def _eval_number(cls, arg): |
| if arg.is_Number: |
| return arg.ceiling() |
| if 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] |
|
|
| @classmethod |
| def _eval_const_number(cls, arg): |
| if arg.is_real: |
| if arg.is_zero: |
| return S.Zero |
| if arg.is_positive: |
| num, den = arg.as_numer_denom() |
| s = den.is_negative |
| if s is None: |
| return None |
| if s: |
| num, den = -num, -den |
| |
| if is_le(num, den): |
| return S.One |
| |
| if fuzzy_and([is_lt(den, num), is_le(num, 2*den)]): |
| return Integer(2) |
| if arg.is_negative: |
| num, den = arg.as_numer_denom() |
| s = den.is_negative |
| if s is None: |
| return None |
| if s: |
| num, den = -num, -den |
| |
| if is_lt(-den, num): |
| return S.Zero |
| |
| if fuzzy_and([is_lt(-2*den, num), is_le(num, -den)]): |
| return S.NegativeOne |
|
|
| def _eval_as_leading_term(self, x, logx, cdir): |
| 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 and other.is_noninteger: |
| return S.true |
| 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 and other.is_noninteger: |
| return S.false |
| if other is S.Infinity and self.is_finite: |
| return S.true |
|
|
| return Le(self, other, evaluate=False) |
|
|
|
|
| @dispatch(ceiling, Basic) |
| def _eval_is_eq(lhs, rhs): |
| return is_eq(lhs.rewrite(floor), rhs) or is_eq(lhs.rewrite(frac),rhs) |
|
|
|
|
| class frac(DefinedFunction): |
| 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 |
| |
| """ |
| @classmethod |
| 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): |
| |
| |
| 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) |
| |
| if other.is_extended_nonpositive: |
| return S.true |
| |
| 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) |
| |
| res = self._value_one_or_more(other) |
| if res is not None: |
| return not(res) |
| |
| 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) |
| |
| if other.is_extended_negative: |
| return S.false |
| |
| 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) |
| |
| if other.is_extended_nonpositive: |
| return S.false |
| |
| 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, cdir): |
| 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 |
|
|
|
|
| @dispatch(frac, Basic) |
| def _eval_is_eq(lhs, rhs): |
| if (lhs.rewrite(floor) == rhs) or \ |
| (lhs.rewrite(ceiling) == rhs): |
| return True |
| |
| if rhs.is_extended_negative: |
| return False |
| |
| res = lhs._value_one_or_more(rhs) |
| if res is not None: |
| return False |
|
|