def _eval_rewrite_as_Sum(self, n, m=None): k = Dummy("k", integer=True) if m is None: m = S.One return C.Sum(k**(-m), (k, 1, n))
def _invert_real(f, g_ys, symbol): """ Helper function for invert_real """ if not f.has(symbol): raise ValueError("Inverse of constant function doesn't exist") if f is symbol: return (f, g_ys) n = Dummy('n') if hasattr(f, 'inverse') and not isinstance(f, TrigonometricFunction) and \ not isinstance(f, HyperbolicFunction): if len(f.args) > 1: raise ValueError("Only functions with one argument are supported.") return _invert_real(f.args[0], imageset(Lambda(n, f.inverse()(n)), g_ys), symbol) if isinstance(f, Abs): return _invert_real(f.args[0], Union(imageset(Lambda(n, n), g_ys).intersect(Interval(0, oo)), imageset(Lambda(n, -n), g_ys).intersect(Interval(-oo, 0))), symbol) if f.is_Add: # f = g + h g, h = f.as_independent(symbol) if g != S.Zero: return _invert_real(h, imageset(Lambda(n, n - g), g_ys), symbol) if f.is_Mul: # f = g*h g, h = f.as_independent(symbol) if g != S.One: return _invert_real(h, imageset(Lambda(n, n/g), g_ys), symbol) if f.is_Pow: base, expo = f.args base_has_sym = base.has(symbol) expo_has_sym = expo.has(symbol) if not expo_has_sym: res = imageset(Lambda(n, real_root(n, expo)), g_ys) if expo.is_rational: numer, denom = expo.as_numer_denom() if numer == S.One or numer == - S.One: return _invert_real(base, res, symbol) else: if numer % 2 == 0: n = Dummy('n') neg_res = imageset(Lambda(n, -n), res) return _invert_real(base, res + neg_res, symbol) else: return _invert_real(base, res, symbol) else: if not base.is_positive: raise ValueError("x**w where w is irrational is not " "defined for negative x") return _invert_real(base, res, symbol) if not base_has_sym: return _invert_real(expo, imageset(Lambda(n, log(n)/log(base)), g_ys), symbol) if isinstance(f, sin): n = Dummy('n') if isinstance(g_ys, FiniteSet): sin_invs = Union(*[imageset(Lambda(n, n*pi + (-1)**n*asin(g_y)), \ S.Integers) for g_y in g_ys]) return _invert_real(f.args[0], sin_invs, symbol) if isinstance(f, csc): n = Dummy('n') if isinstance(g_ys, FiniteSet): csc_invs = Union(*[imageset(Lambda(n, n*pi + (-1)**n*acsc(g_y)), \ S.Integers) for g_y in g_ys]) return _invert_real(f.args[0], csc_invs, symbol) if isinstance(f, cos): n = Dummy('n') if isinstance(g_ys, FiniteSet): cos_invs_f1 = Union(*[imageset(Lambda(n, 2*n*pi + acos(g_y)), \ S.Integers) for g_y in g_ys]) cos_invs_f2 = Union(*[imageset(Lambda(n, 2*n*pi - acos(g_y)), \ S.Integers) for g_y in g_ys]) cos_invs = Union(cos_invs_f1, cos_invs_f2) return _invert_real(f.args[0], cos_invs, symbol) if isinstance(f, sec): n = Dummy('n') if isinstance(g_ys, FiniteSet): sec_invs_f1 = Union(*[imageset(Lambda(n, 2*n*pi + asec(g_y)), \ S.Integers) for g_y in g_ys]) sec_invs_f2 = Union(*[imageset(Lambda(n, 2*n*pi - asec(g_y)), \ S.Integers) for g_y in g_ys]) sec_invs = Union(sec_invs_f1, sec_invs_f2) return _invert_real(f.args[0], sec_invs, symbol) if isinstance(f, tan) or isinstance(f, cot): n = Dummy('n') if isinstance(g_ys, FiniteSet): tan_cot_invs = Union(*[imageset(Lambda(n, n*pi + f.inverse()(g_y)), \ S.Integers) for g_y in g_ys]) return _invert_real(f.args[0], tan_cot_invs, symbol) return (f, g_ys)
def test_issue_7993(): x = Dummy(integer=True) y = Dummy(noninteger=True) assert (x - y).is_zero is False
def eval(cls, arg): from sympy.simplify.simplify import signsimp from sympy.core.function import expand_mul if hasattr(arg, '_eval_Abs'): obj = arg._eval_Abs() if obj is not None: return obj # if not isinstance(arg, Expr): # raise TypeError("Bad argument type for Abs(): %s" % type(arg)) # handle what we can arg = signsimp(arg, evaluate=False) if arg.is_Mul: known = [] unk = [] for t in arg.args: tnew = cls(t) if isinstance(tnew, cls): unk.append(tnew.args[0]) else: known.append(tnew) known = Mul(*known) unk = cls(Mul(*unk), evaluate=False) if unk else S.One return known * unk if arg is S.NaN: return S.NaN if arg is S.ComplexInfinity: return S.Infinity # if arg.is_Power: # base, exponent = arg.as_base_exp() # if base.is_extended_real: # if exponent.is_integer: # if exponent.is_even: # return arg # if base is S.NegativeOne: # return S.One # if isinstance(base, cls) and exponent is S.NegativeOne: # return arg # return Abs(base) ** exponent # if base.is_extended_nonnegative: # return base ** re(exponent) # if base.is_extended_negative: # return (-base) ** re(exponent) * exp(-S.Pi * im(exponent)) # return # elif not base.has(Symbol): # complex base # # express base**exponent as exp(exponent*log(base)) # a, b = log(base).as_real_imag() # z = a + I * b # return exp(re(exponent * z)) # elif not arg.is_complex and not exponent.is_integer: # return arg if isinstance(arg, exp): return exp(re(arg.args[0])) if isinstance(arg, AppliedUndef) or arg.is_set: return if arg.is_Add and arg.has(S.Infinity, S.NegativeInfinity): if any(a.is_infinite for a in arg.as_real_imag()): return S.Infinity if arg.is_zero: return S.Zero if arg.is_extended_nonnegative: return arg if arg.is_extended_nonpositive: return -arg if arg.is_imaginary: arg2 = -S.ImaginaryUnit * arg if arg2.is_extended_nonnegative: return arg2 # reject result if all new conjugates are just wrappers around # an expression that was already in the arg conj = signsimp(arg.conjugate(), evaluate=False) new_conj = conj.atoms(conjugate) - arg.atoms(conjugate) if new_conj and all(arg.has(i.args[0]) for i in new_conj): return if arg != conj and arg != -conj: ignore = arg.atoms(Abs) abs_free_arg = arg.xreplace({i: Dummy(real=True) for i in ignore}) unk = [a for a in abs_free_arg.free_symbols if a.is_extended_real is None] if not unk or not all(conj.has(conjugate(u)) for u in unk): return sqrt(expand_mul(arg * conj))
def test_issue_10024(): x = Dummy('x') assert Mod(x, 2*pi).is_zero is None
there exist n in ZZ and u in k(t) with n, u != 0 such that n*f == Du/u. Either returns (n, u) or None, which means that f cannot be written as the logarithmic derivative of a k(t)-radical. case is one of {'primitive', 'exp', 'tan', 'auto'} for the primitive, hyperexponential, and hypertangent cases, respectively. If case is 'auto', it will attempt to determine the type of the derivation automatically. """ fa, fd = fa.cancel(fd, include=True) # f must be simple n, s = splitfactor(fd, DE) if not s.is_one: pass z = z or Dummy('z') H, b = residue_reduce(fa, fd, DE, z=z) if not b: # I will have to verify, but I believe that the answer should be # None in this case. This should never happen for the # functions given when solving the parametric logarithmic # derivative problem when integration elementary functions (see # Bronstein's book, page 255), so most likely this indicates a bug. return None roots = [(i, i.real_roots()) for i, _ in H] if not all( len(j) == i.degree() and all(k.is_Rational for k in j) for i, j in roots): # If f is the logarithmic derivative of a k(t)-radical, then all the # roots of the resultant must be rational numbers.
from sympy import pi, I from sympy.core import Dummy, sympify from sympy.core.function import Function, ArgumentIndexError from sympy.core.singleton import S from sympy.functions import assoc_legendre from sympy.functions.combinatorial.factorials import factorial from sympy.functions.elementary.complexes import Abs from sympy.functions.elementary.exponential import exp from sympy.functions.elementary.miscellaneous import sqrt from sympy.functions.elementary.trigonometric import sin, cos, cot _x = Dummy("x") class Ynm(Function): r""" Spherical harmonics defined as .. math:: Y_n^m(\theta, \varphi) := \sqrt{\frac{(2n+1)(n-m)!}{4\pi(n+m)!}} \exp(i m \varphi) \mathrm{P}_n^m\left(\cos(\theta)\right) Explanation =========== ``Ynm()`` gives the spherical harmonic function of order $n$ and $m$ in $\theta$ and $\varphi$, $Y_n^m(\theta, \varphi)$. The four parameters are as follows: $n \geq 0$ an integer and $m$ an integer such that $-n \leq m \leq n$ holds. The two angles are real-valued with $\theta \in [0, \pi]$ and $\varphi \in [0, 2\pi]$.
def solve_univariate_inequality(expr, gen, relational=True, domain=S.Reals): """Solves a real univariate inequality. Parameters ========== expr : Relational The target inequality gen : Symbol The variable for which the inequality is solved relational : bool A Relational type output is expected or not domain : Set The domain over which the equation is solved Raises ====== NotImplementedError The solution of the inequality cannot be determined due to limitation in `solvify`. Notes ===== Currently, we cannot solve all the inequalities due to limitations in `solvify`. Also, the solution returned for trigonometric inequalities are restricted in its periodic interval. See Also ======== solvify: solver returning solveset solutions with solve's output API Examples ======== >>> from sympy.solvers.inequalities import solve_univariate_inequality >>> from sympy import Symbol, sin, Interval, S >>> x = Symbol('x') >>> solve_univariate_inequality(x**2 >= 4, x) Or(And(-oo < x, x <= -2), And(2 <= x, x < oo)) >>> solve_univariate_inequality(x**2 >= 4, x, relational=False) (-oo, -2] U [2, oo) >>> domain = Interval(0, S.Infinity) >>> solve_univariate_inequality(x**2 >= 4, x, False, domain) [2, oo) >>> solve_univariate_inequality(sin(x) > 0, x, relational=False) (0, pi) """ from sympy.calculus.util import (continuous_domain, periodicity, function_range) from sympy.solvers.solvers import denoms from sympy.solvers.solveset import solveset_real, solvify # This keeps the function independent of the assumptions about `gen`. # `solveset` makes sure this function is called only when the domain is # real. d = Dummy(real=True) expr = expr.subs(gen, d) _gen = gen gen = d rv = None if expr is S.true: rv = domain elif expr is S.false: rv = S.EmptySet else: e = expr.lhs - expr.rhs period = periodicity(e, gen) if period is not None: frange = function_range(e, gen, domain) rel = expr.rel_op if rel == '<' or rel == '<=': if expr.func(frange.sup, 0): rv = domain elif not expr.func(frange.inf, 0): rv = S.EmptySet elif rel == '>' or rel == '>=': if expr.func(frange.inf, 0): rv = domain elif not expr.func(frange.sup, 0): rv = S.EmptySet inf, sup = domain.inf, domain.sup if sup - inf is S.Infinity: domain = Interval(0, period, False, True) if rv is None: singularities = [] for d in denoms(e): singularities.extend(solvify(d, gen, domain)) domain = continuous_domain(e, gen, domain) solns = solvify(e, gen, domain) if solns is None: raise NotImplementedError(filldedent('''The inequality cannot be solved using solve_univariate_inequality.''')) include_x = expr.func(0, 0) def valid(x): v = e.subs(gen, x) try: r = expr.func(v, 0) except TypeError: r = S.false if r in (S.true, S.false): return r if v.is_real is False: return S.false else: v = v.n(2) if v.is_comparable: return expr.func(v, 0) return S.false start = domain.inf sol_sets = [S.EmptySet] try: discontinuities = domain.boundary - FiniteSet(domain.inf, domain.sup) critical_points = set(solns + singularities + list(discontinuities)) reals = _nsort(critical_points, separated=True)[0] except NotImplementedError: raise NotImplementedError('sorting of these roots is not supported') if valid(start) and start.is_finite: sol_sets.append(FiniteSet(start)) for x in reals: end = x if end in [S.NegativeInfinity, S.Infinity]: if valid(S(0)): sol_sets.append(Interval(start, S.Infinity, True, True)) break pt = ((start + end)/2 if start is not S.NegativeInfinity else (end/2 if end.is_positive else (2*end if end.is_negative else end - 1))) if valid(pt): sol_sets.append(Interval(start, end, True, True)) if x in singularities: singularities.remove(x) elif include_x: sol_sets.append(FiniteSet(x)) start = end end = domain.sup # in case start == -oo then there were no solutions so we just # check a point between -oo and oo (e.g. 0) else pick a point # past the last solution (which is start after the end of the # for-loop above pt = (0 if start is S.NegativeInfinity else (start/2 if start.is_negative else (2*start if start.is_positive else start + 1))) if pt >= end: pt = (start + end)/2 if valid(pt): sol_sets.append(Interval(start, end, True, True)) rv = Union(*sol_sets).subs(gen, _gen) return rv if not relational else rv.as_relational(_gen)
def deltaproduct(f, limit): """ Handle products containing a KroneckerDelta. See Also ======== deltasummation sympy.functions.special.tensor_functions.KroneckerDelta sympy.concrete.products.product """ from sympy.concrete.products import product if ((limit[2] - limit[1]) < 0) == True: return S.One if not f.has(KroneckerDelta): return product(f, limit) if f.is_Add: # Identify the term in the Add that has a simple KroneckerDelta delta = None terms = [] for arg in sorted(f.args, key=default_sort_key): if delta is None and _has_simple_delta(arg, limit[0]): delta = arg else: terms.append(arg) newexpr = f.func(*terms) k = Dummy("kprime", integer=True) if isinstance(limit[1], int) and isinstance(limit[2], int): result = deltaproduct(newexpr, limit) + sum([ deltaproduct(newexpr, (limit[0], limit[1], ik - 1)) * delta.subs(limit[0], ik) * deltaproduct(newexpr, (limit[0], ik + 1, limit[2])) for ik in range(int(limit[1]), int(limit[2] + 1)) ]) else: result = deltaproduct(newexpr, limit) + deltasummation( deltaproduct(newexpr, (limit[0], limit[1], k - 1)) * delta.subs(limit[0], k) * deltaproduct(newexpr, (limit[0], k + 1, limit[2])), (k, limit[1], limit[2]), no_piecewise=_has_simple_delta(newexpr, limit[0])) return _remove_multiple_delta(result) delta, _ = _extract_delta(f, limit[0]) if not delta: g = _expand_delta(f, limit[0]) if f != g: from sympy import factor try: return factor(deltaproduct(g, limit)) except AssertionError: return deltaproduct(g, limit) return product(f, limit) from sympy import Eq c = Eq(limit[2], limit[1] - 1) return _remove_multiple_delta(f.subs(limit[0], limit[1])*KroneckerDelta(limit[2], limit[1])) + \ S.One*_simplify_delta(KroneckerDelta(limit[2], limit[1] - 1))
def tangent_lines(self, p): """Tangent lines between `p` and the ellipse. If `p` is on the ellipse, returns the tangent line through point `p`. Otherwise, returns the tangent line(s) from `p` to the ellipse, or None if no tangent line is possible (e.g., `p` inside ellipse). Parameters ---------- p : Point Returns ------- tangent_lines : list with 1 or 2 Lines Raises ------ NotImplementedError Can only find tangent lines for a point, `p`, on the ellipse. See Also -------- Point Line Examples -------- >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.tangent_lines(Point(3, 0)) [Line(Point(3, 0), Point(3, -12))] >>> # This will plot an ellipse together with a tangent line. >>> from sympy import Point, Ellipse, Plot >>> e = Ellipse(Point(0,0), 3, 2) >>> t = e.tangent_lines(e.random_point()) # doctest: +SKIP >>> p = Plot() # doctest: +SKIP >>> p[0] = e # doctest: +SKIP >>> p[1] = t # doctest: +SKIP """ from sympy import solve if self.encloses_point(p): return [] if p in self: rise = (self.vradius**2) * (self.center[0] - p[0]) run = (self.hradius**2) * (p[1] - self.center[1]) p2 = Point(simplify(p[0] + run), simplify(p[1] + rise)) return [Line(p, p2)] else: if len(self.foci) == 2: f1, f2 = self.foci maj = self.hradius test = (2 * maj - Point.distance(f1, p) - Point.distance(f2, p)) else: test = self.radius - Point.distance(self.center, p) if test.is_number and test.is_positive: return [] # else p is outside the ellipse or we can't tell. In case of the # latter, the solutions returned will only be valid if # the point is not inside the ellipse; if it is, nan will result. x, y = Dummy('x'), Dummy('y') eq = self.equation(x, y) dydx = idiff(eq, y, x) slope = Line(p, Point(x, y)).slope tangent_points = solve( [w.as_numer_denom()[0] for w in [slope - dydx, eq]], [x, y]) # handle horizontal and vertical tangent lines if len(tangent_points) == 1: assert tangent_points[0][0] == p[0] or tangent_points[0][ 1] == p[1] return [ Line(p, Point(p[0] + 1, p[1])), Line(p, Point(p[0], p[1] + 1)) ] # others return [Line(p, tangent_points[0]), Line(p, tangent_points[1])]
def intersection(self, o): """ The intersection with other geometrical entity. Parameters ========== Point, Point3D, LinearEntity, LinearEntity3D, Plane Returns ======= List Examples ======== >>> from sympy import Point, Point3D, Line, Line3D, Plane >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1)) >>> b = Point3D(1, 2, 3) >>> a.intersection(b) [Point3D(1, 2, 3)] >>> c = Line3D(Point3D(1, 4, 7), Point3D(2, 2, 2)) >>> a.intersection(c) [Point3D(2, 2, 2)] >>> d = Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3)) >>> e = Plane(Point3D(2, 0, 0), normal_vector=(3, 4, -3)) >>> d.intersection(e) [Line3D(Point3D(78/23, -24/23, 0), Point3D(147/23, 321/23, 23))] """ from sympy.geometry.line3d import LinearEntity3D from sympy.geometry.line import LinearEntity if isinstance(o, (Point, Point3D)): if o in self: return [Point3D(o)] else: return [] if isinstance(o, (LinearEntity, LinearEntity3D)): if o in self: p1, p2 = o.p1, o.p2 if isinstance(o, Segment): o = Segment3D(p1, p2) elif isinstance(o, Ray): o = Ray3D(p1, p2) elif isinstance(o, Line): o = Line3D(p1, p2) else: raise ValueError('unhandled linear entity: %s' % o.func) return [o] else: x, y, z = map(Dummy, 'xyz') t = Dummy() # unnamed else it may clash with a symbol in o a = Point3D(o.arbitrary_point(t)) b = self.equation(x, y, z) c = solve(b.subs(list(zip((x, y, z), a.args))), t) if not c: return [] else: p = a.subs(t, c[0]) if p not in self: return [] # e.g. a segment might not intersect a plane return [p] if isinstance(o, Plane): if o == self: return [self] if self.is_parallel(o): return [] else: x, y, z = map(Dummy, 'xyz') a, b = Matrix([self.normal_vector]), Matrix([o.normal_vector]) c = list(a.cross(b)) d = self.equation(x, y, z) e = o.equation(x, y, z) f = solve((d.subs(z, 0), e.subs(z, 0)), [x, y]) if len(f) == 2: return [Line3D(Point3D(f[x], f[y], 0), direction_ratio=c)] g = solve((d.subs(y, 0), e.subs(y, 0)), [x, z]) if len(g) == 2: return [Line3D(Point3D(g[x], 0, g[z]), direction_ratio=c)] h = solve((d.subs(x, 0), e.subs(x, 0)), [y, z]) if len(h) == 2: return [Line3D(Point3D(0, h[y], h[z]), direction_ratio=c)]
def intersection(self, o): """ The intersection with other geometrical entity. Parameters ========== Point, Point3D, LinearEntity, LinearEntity3D, Plane Returns ======= List Examples ======== >>> from sympy import Point, Point3D, Line, Line3D, Plane >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1)) >>> b = Point3D(1, 2, 3) >>> a.intersection(b) [Point3D(1, 2, 3)] >>> c = Line3D(Point3D(1, 4, 7), Point3D(2, 2, 2)) >>> a.intersection(c) [Point3D(2, 2, 2)] >>> d = Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3)) >>> e = Plane(Point3D(2, 0, 0), normal_vector=(3, 4, -3)) >>> d.intersection(e) [Line3D(Point3D(78/23, -24/23, 0), Point3D(147/23, 321/23, 23))] """ from sympy.geometry.line import LinearEntity, LinearEntity3D if not isinstance(o, GeometryEntity): o = Point(o, dim=3) if isinstance(o, Point): if o in self: return [o] else: return [] if isinstance(o, (LinearEntity, LinearEntity3D)): # recast to 3D p1, p2 = o.p1, o.p2 if isinstance(o, Segment): o = Segment3D(p1, p2) elif isinstance(o, Ray): o = Ray3D(p1, p2) elif isinstance(o, Line): o = Line3D(p1, p2) else: raise ValueError('unhandled linear entity: %s' % o.func) if o in self: return [o] else: t = Dummy() # unnamed else it may clash with a symbol in o a = Point3D(o.arbitrary_point(t)) p1, n = self.p1, Point3D(self.normal_vector) # TODO: Replace solve with solveset, when this line is tested c = solve((a - p1).dot(n), t) if not c: return [] else: c = [i for i in c if i.is_real is not False] if len(c) > 1: c = [i for i in c if i.is_real] if len(c) != 1: raise Undecidable("not sure which point is real") p = a.subs(t, c[0]) if p not in o: return [] # e.g. a segment might not intersect a plane return [p] if isinstance(o, Plane): if self.equals(o): return [self] if self.is_parallel(o): return [] else: x, y, z = map(Dummy, 'xyz') a, b = Matrix([self.normal_vector]), Matrix([o.normal_vector]) c = list(a.cross(b)) d = self.equation(x, y, z) e = o.equation(x, y, z) result = list(linsolve([d, e], x, y, z))[0] for i in (x, y, z): result = result.subs(i, 0) return [Line3D(Point3D(result), direction_ratio=c)]
def dummies(name): d = Dummy(name) while True: yield d
def _eval_rewrite_as_Product(self, n): if not (n.is_integer and n.is_nonnegative): return self k = Dummy('k', integer=True, positive=True) return C.Product((n + k) / k, (k, 2, n))
def __new__(cls, expr, *args, **kwargs): expr = sympify(expr) if not args: if expr.is_Order: variables = expr.variables point = expr.point else: variables = list(expr.free_symbols) point = [S.Zero]*len(variables) else: args = list(args if is_sequence(args) else [args]) variables, point = [], [] if is_sequence(args[0]): for a in args: v, p = list(map(sympify, a)) variables.append(v) point.append(p) else: variables = list(map(sympify, args)) point = [S.Zero]*len(variables) if not all(v.is_Symbol for v in variables): raise TypeError('Variables are not symbols, got %s' % variables) if len(list(uniq(variables))) != len(variables): raise ValueError('Variables are supposed to be unique symbols, got %s' % variables) if expr.is_Order: expr_vp = dict(expr.args[1:]) new_vp = dict(expr_vp) vp = dict(zip(variables, point)) for v, p in vp.items(): if v in new_vp.keys(): if p != new_vp[v]: raise NotImplementedError( "Mixing Order at different points is not supported.") else: new_vp[v] = p if set(expr_vp.keys()) == set(new_vp.keys()): return expr else: variables = list(new_vp.keys()) point = [new_vp[v] for v in variables] if expr is S.NaN: return S.NaN if any(x in p.free_symbols for x in variables for p in point): raise ValueError('Got %s as a point.' % point) if variables: if any(p != point[0] for p in point): raise NotImplementedError if point[0] is S.Infinity: s = {k: 1/Dummy() for k in variables} rs = {1/v: 1/k for k, v in s.items()} elif point[0] is not S.Zero: s = dict((k, Dummy() + point[0]) for k in variables) rs = dict((v - point[0], k - point[0]) for k, v in s.items()) else: s = () rs = () expr = expr.subs(s) if expr.is_Add: from sympy import expand_multinomial expr = expand_multinomial(expr) if s: args = tuple([r[0] for r in rs.items()]) else: args = tuple(variables) if len(variables) > 1: # XXX: better way? We need this expand() to # workaround e.g: expr = x*(x + y). # (x*(x + y)).as_leading_term(x, y) currently returns # x*y (wrong order term!). That's why we want to deal with # expand()'ed expr (handled in "if expr.is_Add" branch below). expr = expr.expand() if expr.is_Add: lst = expr.extract_leading_order(args) expr = Add(*[f.expr for (e, f) in lst]) elif expr: expr = expr.as_leading_term(*args) expr = expr.as_independent(*args, as_Add=False)[1] expr = expand_power_base(expr) expr = expand_log(expr) if len(args) == 1: # The definition of O(f(x)) symbol explicitly stated that # the argument of f(x) is irrelevant. That's why we can # combine some power exponents (only "on top" of the # expression tree for f(x)), e.g.: # x**p * (-x)**q -> x**(p+q) for real p, q. x = args[0] margs = list(Mul.make_args( expr.as_independent(x, as_Add=False)[1])) for i, t in enumerate(margs): if t.is_Pow: b, q = t.args if b in (x, -x) and q.is_real and not q.has(x): margs[i] = x**q elif b.is_Pow and not b.exp.has(x): b, r = b.args if b in (x, -x) and r.is_real: margs[i] = x**(r*q) elif b.is_Mul and b.args[0] is S.NegativeOne: b = -b if b.is_Pow and not b.exp.has(x): b, r = b.args if b in (x, -x) and r.is_real: margs[i] = x**(r*q) expr = Mul(*margs) expr = expr.subs(rs) if expr is S.Zero: return expr if expr.is_Order: expr = expr.expr if not expr.has(*variables): expr = S.One # create Order instance: vp = dict(zip(variables, point)) variables.sort(key=default_sort_key) point = [vp[v] for v in variables] args = (expr,) + Tuple(*zip(variables, point)) obj = Expr.__new__(cls, *args) return obj
def eval(cls, *_args): """Either return a modified version of the args or, if no modifications were made, return None. Modifications that are made here: 1) relationals are made canonical 2) any False conditions are dropped 3) any repeat of a previous condition is ignored 3) any args past one with a true condition are dropped If there are no args left, nan will be returned. If there is a single arg with a True condition, its corresponding expression will be returned. """ if not _args: return Undefined if len(_args) == 1 and _args[0][-1] == True: return _args[0][0] newargs = [] # the unevaluated conditions current_cond = set() # the conditions up to a given e, c pair # make conditions canonical args = [] for e, c in _args: if not c.is_Atom and not isinstance(c, Relational): free = c.free_symbols if len(free) == 1: funcs = [ i for i in c.atoms(Function) if not isinstance(i, Boolean) ] if len(funcs) == 1 and len( c.xreplace({ list(funcs)[0]: Dummy() }).free_symbols) == 1: # we can treat function like a symbol free = funcs _c = c x = free.pop() try: c = c.as_set().as_relational(x) except NotImplementedError: pass else: reps = {} for i in c.atoms(Relational): ic = i.canonical if ic.rhs in (S.Infinity, S.NegativeInfinity): if not _c.has(ic.rhs): # don't accept introduction of # new Relationals with +/-oo reps[i] = S.true elif ('=' not in ic.rel_op and c.xreplace( {x: i.rhs}) != _c.xreplace({x: i.rhs})): reps[i] = Relational( i.lhs, i.rhs, i.rel_op + '=') c = c.xreplace(reps) args.append((e, _canonical(c))) for expr, cond in args: # Check here if expr is a Piecewise and collapse if one of # the conds in expr matches cond. This allows the collapsing # of Piecewise((Piecewise((x,x<0)),x<0)) to Piecewise((x,x<0)). # This is important when using piecewise_fold to simplify # multiple Piecewise instances having the same conds. # Eventually, this code should be able to collapse Piecewise's # having different intervals, but this will probably require # using the new assumptions. if isinstance(expr, Piecewise): unmatching = [] for i, (e, c) in enumerate(expr.args): if c in current_cond: # this would already have triggered continue if c == cond: if c != True: # nothing past this condition will ever # trigger and only those args before this # that didn't match a previous condition # could possibly trigger if unmatching: expr = Piecewise(*(unmatching + [(e, c)])) else: expr = e break else: unmatching.append((e, c)) # check for condition repeats got = False # -- if an And contains a condition that was # already encountered, then the And will be # False: if the previous condition was False # then the And will be False and if the previous # condition is True then then we wouldn't get to # this point. In either case, we can skip this condition. for i in ([cond] + (list(cond.args) if isinstance(cond, And) else [])): if i in current_cond: got = True break if got: continue # -- if not(c) is already in current_cond then c is # a redundant condition in an And. This does not # apply to Or, however: (e1, c), (e2, Or(~c, d)) # is not (e1, c), (e2, d) because if c and d are # both False this would give no results when the # true answer should be (e2, True) if isinstance(cond, And): nonredundant = [] for c in cond.args: if (isinstance(c, Relational) and (~c).canonical in current_cond): continue nonredundant.append(c) cond = cond.func(*nonredundant) elif isinstance(cond, Relational): if (~cond).canonical in current_cond: cond = S.true current_cond.add(cond) # collect successive e,c pairs when exprs or cond match if newargs: if newargs[-1].expr == expr: orcond = Or(cond, newargs[-1].cond) if isinstance(orcond, (And, Or)): orcond = distribute_and_over_or(orcond) newargs[-1] = ExprCondPair(expr, orcond) continue elif newargs[-1].cond == cond: orexpr = Or(expr, newargs[-1].expr) if isinstance(orexpr, (And, Or)): orexpr = distribute_and_over_or(orexpr) newargs[-1] == ExprCondPair(orexpr, cond) continue newargs.append(ExprCondPair(expr, cond)) # some conditions may have been redundant missing = len(newargs) != len(_args) # some conditions may have changed same = all(a == b for a, b in zip(newargs, _args)) # if either change happened we return the expr with the # updated args if not newargs: raise ValueError( filldedent(''' There are no conditions (or none that are not trivially false) to define an expression.''')) if missing or not same: return cls(*newargs)
def apart_full_decomposition(P, Q): """ Bronstein's full partial fraction decomposition algorithm. Given a univariate rational function ``f``, performing only GCD operations over the algebraic closure of the initial ground domain of definition, compute full partial fraction decomposition with fractions having linear denominators. Note that no factorization of the initial denominator of ``f`` is performed. The final decomposition is formed in terms of a sum of :class:`RootSum` instances. References ========== 1. [Bronstein93]_ """ f, x, U = P / Q, P.gen, [] u = Function('u')(x) a = Dummy('a') partial = S(0) for d, n in Q.sqf_list_include(all=True): b = d.as_expr() U += [u.diff(x, n - 1)] h = cancel(f * b**n) / u**n H, subs = [h], [] for j in range(1, n): H += [H[-1].diff(x) / j] for j in range(1, n + 1): subs += [(U[j - 1], b.diff(x, j) / j)] for j in range(0, n): P, Q = cancel(H[j]).as_numer_denom() for i in range(0, j + 1): P = P.subs(*subs[j - i]) Q = Q.subs(*subs[0]) P = Poly(P, x) Q = Poly(Q, x) G = P.gcd(d) D = d.quo(G) B, g = Q.half_gcdex(D) b = (P * B.quo(g)).rem(D) numer = b.as_expr() denom = (x - a)**(n - j) func = Lambda(a, numer.subs(x, a) / denom) partial += RootSum(D, func, auto=False) return partial
def _eval_interval(self, sym, a, b, _first=True): """Evaluates the function along the sym in a given interval [a, b]""" # FIXME: Currently complex intervals are not supported. A possible # replacement algorithm, discussed in issue 5227, can be found in the # following papers; # http://portal.acm.org/citation.cfm?id=281649 # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4127&rep=rep1&type=pdf from sympy.core.symbol import Dummy if a is None or b is None: # In this case, it is just simple substitution return super(Piecewise, self)._eval_interval(sym, a, b) else: x, lo, hi = map(as_Basic, (sym, a, b)) if _first: # get only x-dependent relationals def handler(ipw): if isinstance(ipw, self.func): return ipw._eval_interval(x, lo, hi, _first=None) else: return ipw._eval_interval(x, lo, hi) irv = self._handle_irel(x, handler) if irv is not None: return irv if (lo < hi) is S.false or (lo is S.Infinity or hi is S.NegativeInfinity): rv = self._eval_interval(x, hi, lo, _first=False) if isinstance(rv, Piecewise): rv = Piecewise(*[(-e, c) for e, c in rv.args]) else: rv = -rv return rv if (lo < hi) is S.true or (hi is S.Infinity or lo is S.NegativeInfinity): pass else: _a = Dummy('lo') _b = Dummy('hi') a = lo if lo.is_comparable else _a b = hi if hi.is_comparable else _b pos = self._eval_interval(x, a, b, _first=False) if a == _a and b == _b: # it's purely symbolic so just swap lo and hi and # change the sign to get the value for when lo > hi neg, pos = (-pos.xreplace({ _a: hi, _b: lo }), pos.xreplace({ _a: lo, _b: hi })) else: # at least one of the bounds was comparable, so allow # _eval_interval to use that information when computing # the interval with lo and hi reversed neg, pos = (-self._eval_interval(x, hi, lo, _first=False), pos.xreplace({ _a: lo, _b: hi })) # allow simplification based on ordering of lo and hi p = Dummy('', positive=True) if lo.is_Symbol: pos = pos.xreplace({lo: hi - p}).xreplace({p: hi - lo}) neg = neg.xreplace({lo: hi + p}).xreplace({p: lo - hi}) elif hi.is_Symbol: pos = pos.xreplace({hi: lo + p}).xreplace({p: hi - lo}) neg = neg.xreplace({hi: lo - p}).xreplace({p: lo - hi}) # assemble return expression; make the first condition be Lt # b/c then the first expression will look the same whether # the lo or hi limit is symbolic if a == _a: # the lower limit was symbolic rv = Piecewise((pos, lo < hi), (neg, True)) else: rv = Piecewise((neg, hi < lo), (pos, True)) if rv == Undefined: raise ValueError( "Can't integrate across undefined region.") if any(isinstance(i, Piecewise) for i in (pos, neg)): rv = piecewise_fold(rv) return rv # handle a Piecewise with lo <= hi and no x-independent relationals # ----------------------------------------------------------------- try: abei = self._intervals(x) except NotImplementedError: from sympy import Integral # not being able to do the interval of f(x) can # be stated as not being able to do the integral # of f'(x) over the same range return Integral(self.diff(x), (x, lo, hi)) # unevaluated pieces = [(a, b) for a, b, _, _ in abei] done = [(lo, hi, -1)] oo = S.Infinity for k, p in enumerate(pieces): if p[:2] == (-oo, oo): # all undone intervals will get this key for j, (a, b, i) in enumerate(done): if i == -1: done[j] = a, b, k break # nothing else to consider N = len(done) - 1 for j, (a, b, i) in enumerate(reversed(done)): if i == -1: j = N - j done[j:j + 1] = _clip(p, (a, b), k) done = [(a, b, i) for a, b, i in done if a != b] # return the sum of the intervals sum = S.Zero upto = None for a, b, i in done: if i == -1: if upto is None: return Undefined # TODO simplify hi <= upto return Piecewise((sum, hi <= upto), (Undefined, True)) sum += abei[i][-2]._eval_interval(x, a, b) upto = b return sum
def parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None): """ Parametric logarithmic derivative heuristic. Given a derivation D on k[t], f in k(t), and a hyperexponential monomial theta over k(t), raises either NotImplementedError, in which case the heuristic failed, or returns None, in which case it has proven that no solution exists, or returns a solution (n, m, v) of the equation n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0. If this heuristic fails, the structure theorem approach will need to be used. The argument w == Dtheta/theta """ # TODO: finish writing this and write tests c1 = c1 or Dummy('c1') p, a = fa.div(fd) q, b = wa.div(wd) B = max(0, derivation(DE.t, DE).degree(DE.t) - 1) C = max(p.degree(DE.t), q.degree(DE.t)) if q.degree(DE.t) > B: eqs = [p.nth(i) - c1 * q.nth(i) for i in range(B + 1, C + 1)] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) > B, no solution for c. return None N, M = s[c1].as_numer_denom() # N and M are integers N, M = Poly(N, DE.t), Poly(M, DE.t) nfmwa = N * fa * wd - M * wa * fd nfmwd = fd * wd Qv = is_log_deriv_k_t_radical_in_field(N * fa * wd - M * wa * fd, fd * wd, DE, 'auto') if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, e, v = Qv if e != 1: return None if Q.is_zero or v.is_zero: return None return (Q * N, Q * M, v) if p.degree(DE.t) > B: return None c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC()) l = fd.monic().lcm(wd.monic()) * Poly(c, DE.t) ln, ls = splitfactor(l, DE) z = ls * ln.gcd(ln.diff(DE.t)) if not z.has(DE.t): raise NotImplementedError("parametric_log_deriv_heu() " "heuristic failed: z in k.") u1, r1 = (fa * l.quo(fd)).div(z) # (l*f).div(z) u2, r2 = (wa * l.quo(wd)).div(z) # (l*w).div(z) eqs = [r1.nth(i) - c1 * r2.nth(i) for i in range(z.degree(DE.t))] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) <= B, no solution for c. return None M, N = s[c1].as_numer_denom() nfmwa = N.as_poly(DE.t) * fa * wd - M.as_poly(DE.t) * wa * fd nfmwd = fd * wd Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE) if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, v = Qv if Q.is_zero or v.is_zero: return None return (Q * N, Q * M, v)
def besselsimp(expr): """ Simplify bessel-type functions. This routine tries to simplify bessel-type functions. Currently it only works on the Bessel J and I functions, however. It works by looking at all such functions in turn, and eliminating factors of "I" and "-1" (actually their polar equivalents) in front of the argument. Then, functions of half-integer order are rewritten using strigonometric functions and functions of integer order (> 1) are rewritten using functions of low order. Finally, if the expression was changed, compute factorization of the result with factor(). >>> from sympy import besselj, besseli, besselsimp, polar_lift, I, S >>> from sympy.abc import z, nu >>> besselsimp(besselj(nu, z*polar_lift(-1))) exp(I*pi*nu)*besselj(nu, z) >>> besselsimp(besseli(nu, z*polar_lift(-I))) exp(-I*pi*nu/2)*besselj(nu, z) >>> besselsimp(besseli(S(-1)/2, z)) sqrt(2)*cosh(z)/(sqrt(pi)*sqrt(z)) >>> besselsimp(z*besseli(0, z) + z*(besseli(2, z))/2 + besseli(1, z)) 3*z*besseli(0, z)/2 """ # TODO # - better algorithm? # - simplify (cos(pi*b)*besselj(b,z) - besselj(-b,z))/sin(pi*b) ... # - use contiguity relations? def replacer(fro, to, factors): factors = set(factors) def repl(nu, z): if factors.intersection(Mul.make_args(z)): return to(nu, z) return fro(nu, z) return repl def torewrite(fro, to): def tofunc(nu, z): return fro(nu, z).rewrite(to) return tofunc def tominus(fro): def tofunc(nu, z): return exp(I*pi*nu)*fro(nu, exp_polar(-I*pi)*z) return tofunc orig_expr = expr ifactors = [I, exp_polar(I*pi/2), exp_polar(-I*pi/2)] expr = expr.replace( besselj, replacer(besselj, torewrite(besselj, besseli), ifactors)) expr = expr.replace( besseli, replacer(besseli, torewrite(besseli, besselj), ifactors)) minusfactors = [-1, exp_polar(I*pi)] expr = expr.replace( besselj, replacer(besselj, tominus(besselj), minusfactors)) expr = expr.replace( besseli, replacer(besseli, tominus(besseli), minusfactors)) z0 = Dummy('z') def expander(fro): def repl(nu, z): if (nu % 1) == S(1)/2: return exptrigsimp(trigsimp(unpolarify( fro(nu, z0).rewrite(besselj).rewrite(jn).expand( func=True)).subs(z0, z))) elif nu.is_Integer and nu > 1: return fro(nu, z).expand(func=True) return fro(nu, z) return repl expr = expr.replace(besselj, expander(besselj)) expr = expr.replace(bessely, expander(bessely)) expr = expr.replace(besseli, expander(besseli)) expr = expr.replace(besselk, expander(besselk)) if expr != orig_expr: expr = expr.factor() return expr
def _eval_rewrite_as_Product(self, n, **kwargs): from sympy import Product if n.is_nonnegative and n.is_integer: i = Dummy('i', integer=True) return Product(i, (i, 1, n))
def _solve_as_poly(f, symbol, solveset_solver, invert_func): """ Solve the equation using polynomial techniques if it already is a polynomial equation or, with a change of variables, can be made so. """ result = None if f.is_polynomial(symbol): solns = roots(f, symbol, cubics=True, quartics=True, quintics=True, domain='EX') num_roots = sum(solns.values()) if degree(f, symbol) <= num_roots: result = FiniteSet(*solns.keys()) else: poly = Poly(f, symbol) solns = poly.all_roots() if poly.degree() <= len(solns): result = FiniteSet(*solns) else: raise NotImplementedError("Couldn't find all roots " "of the equation %s" % f) else: poly = Poly(f) if poly is None: raise NotImplementedError("Could not convert %s to Poly" % f) gens = [g for g in poly.gens if g.has(symbol)] if len(gens) == 1: poly = Poly(poly, gens[0]) gen = poly.gen deg = poly.degree() poly = Poly(poly.as_expr(), poly.gen, composite=True) poly_solns = FiniteSet( *roots(poly, cubics=True, quartics=True, quintics=True).keys()) if len(poly_solns) < deg: raise NotImplementedError("Couldn't find all the roots of " "the equation %s" % f) if gen != symbol: y = Dummy('y') lhs, rhs_s = invert_func(gen, y, symbol) if lhs is symbol: result = Union(*[rhs_s.subs(y, s) for s in poly_solns]) else: raise NotImplementedError("inversion of %s not handled" % gen) else: raise NotImplementedError("multiple generators not handled" " by solveset") if result is not None: if isinstance(result, FiniteSet): # this is to simplify solutions like -sqrt(-I) to sqrt(2)/2 # - sqrt(2)*I/2. We are not expanding for solution with free # variables because that makes the solution more complicated. For # example expand_complex(a) returns re(a) + I*im(a) if all([ s.free_symbols == set() and not isinstance(s, RootOf) for s in result ]): s = Dummy('s') result = imageset(Lambda(s, expand_complex(s)), result) return result else: raise NotImplementedError
def solve_univariate_inequality(expr, gen, relational=True, domain=S.Reals, continuous=False): """Solves a real univariate inequality. Parameters ========== expr : Relational The target inequality gen : Symbol The variable for which the inequality is solved relational : bool A Relational type output is expected or not domain : Set The domain over which the equation is solved continuous: bool True if expr is known to be continuous over the given domain (and so continuous_domain() doesn't need to be called on it) Raises ====== NotImplementedError The solution of the inequality cannot be determined due to limitation in `solvify`. Notes ===== Currently, we cannot solve all the inequalities due to limitations in `solvify`. Also, the solution returned for trigonometric inequalities are restricted in its periodic interval. See Also ======== solvify: solver returning solveset solutions with solve's output API Examples ======== >>> from sympy.solvers.inequalities import solve_univariate_inequality >>> from sympy import Symbol, sin, Interval, S >>> x = Symbol('x') >>> solve_univariate_inequality(x**2 >= 4, x) ((2 <= x) & (x < oo)) | ((x <= -2) & (-oo < x)) >>> solve_univariate_inequality(x**2 >= 4, x, relational=False) Union(Interval(-oo, -2), Interval(2, oo)) >>> domain = Interval(0, S.Infinity) >>> solve_univariate_inequality(x**2 >= 4, x, False, domain) Interval(2, oo) >>> solve_univariate_inequality(sin(x) > 0, x, relational=False) Interval.open(0, pi) """ from sympy import im from sympy.calculus.util import (continuous_domain, periodicity, function_range) from sympy.solvers.solvers import denoms from sympy.solvers.solveset import solveset_real, solvify, solveset from sympy.solvers.solvers import solve # This keeps the function independent of the assumptions about `gen`. # `solveset` makes sure this function is called only when the domain is # real. _gen = gen _domain = domain if gen.is_real is False: rv = S.EmptySet return rv if not relational else rv.as_relational(_gen) elif gen.is_real is None: gen = Dummy('gen', real=True) try: expr = expr.xreplace({_gen: gen}) except TypeError: raise TypeError( filldedent(''' When gen is real, the relational has a complex part which leads to an invalid comparison like I < 0. ''')) rv = None if expr is S.true: rv = domain elif expr is S.false: rv = S.EmptySet else: e = expr.lhs - expr.rhs period = periodicity(e, gen) if period is S.Zero: e = expand_mul(e) const = expr.func(e, 0) if const is S.true: rv = domain elif const is S.false: rv = S.EmptySet elif period is not None: frange = function_range(e, gen, domain) rel = expr.rel_op if rel == '<' or rel == '<=': if expr.func(frange.sup, 0): rv = domain elif not expr.func(frange.inf, 0): rv = S.EmptySet elif rel == '>' or rel == '>=': if expr.func(frange.inf, 0): rv = domain elif not expr.func(frange.sup, 0): rv = S.EmptySet inf, sup = domain.inf, domain.sup if sup - inf is S.Infinity: domain = Interval(0, period, False, True) if rv is None: n, d = e.as_numer_denom() try: if gen not in n.free_symbols and len(e.free_symbols) > 1: raise ValueError # this might raise ValueError on its own # or it might give None... solns = solvify(e, gen, domain) if solns is None: # in which case we raise ValueError raise ValueError except (ValueError, NotImplementedError): # replace gen with generic x since it's # univariate anyway raise NotImplementedError( filldedent(''' The inequality, %s, cannot be solved using solve_univariate_inequality. ''' % expr.subs(gen, Symbol('x')))) expanded_e = expand_mul(e) def valid(x): # this is used to see if gen=x satisfies the # relational by substituting it into the # expanded form and testing against 0, e.g. # if expr = x*(x + 1) < 2 then e = x*(x + 1) - 2 # and expanded_e = x**2 + x - 2; the test is # whether a given value of x satisfies # x**2 + x - 2 < 0 # # expanded_e, expr and gen used from enclosing scope v = expanded_e.subs(gen, expand_mul(x)) try: r = expr.func(v, 0) except TypeError: r = S.false if r in (S.true, S.false): return r if v.is_real is False: return S.false else: v = v.n(2) if v.is_comparable: return expr.func(v, 0) # not comparable or couldn't be evaluated raise NotImplementedError( 'relationship did not evaluate: %s' % r) singularities = [] for d in denoms(expr, gen): singularities.extend(solvify(d, gen, domain)) if not continuous: domain = continuous_domain(expanded_e, gen, domain) include_x = '=' in expr.rel_op and expr.rel_op != '!=' try: discontinuities = set(domain.boundary - FiniteSet(domain.inf, domain.sup)) # remove points that are not between inf and sup of domain critical_points = FiniteSet( *(solns + singularities + list(discontinuities))).intersection( Interval(domain.inf, domain.sup, domain.inf not in domain, domain.sup not in domain)) if all(r.is_number for r in critical_points): reals = _nsort(critical_points, separated=True)[0] else: sifted = sift(critical_points, lambda x: x.is_real) if sifted[None]: # there were some roots that weren't known # to be real raise NotImplementedError try: reals = sifted[True] if len(reals) > 1: reals = list(sorted(reals)) except TypeError: raise NotImplementedError except NotImplementedError: raise NotImplementedError( 'sorting of these roots is not supported') # If expr contains imaginary coefficients, only take real # values of x for which the imaginary part is 0 make_real = S.Reals if im(expanded_e) != S.Zero: check = True im_sol = FiniteSet() try: a = solveset(im(expanded_e), gen, domain) if not isinstance(a, Interval): for z in a: if z not in singularities and valid( z) and z.is_real: im_sol += FiniteSet(z) else: start, end = a.inf, a.sup for z in _nsort(critical_points + FiniteSet(end)): valid_start = valid(start) if start != end: valid_z = valid(z) pt = _pt(start, z) if pt not in singularities and pt.is_real and valid( pt): if valid_start and valid_z: im_sol += Interval(start, z) elif valid_start: im_sol += Interval.Ropen(start, z) elif valid_z: im_sol += Interval.Lopen(start, z) else: im_sol += Interval.open(start, z) start = z for s in singularities: im_sol -= FiniteSet(s) except (TypeError): im_sol = S.Reals check = False if isinstance(im_sol, EmptySet): raise ValueError( filldedent(''' %s contains imaginary parts which cannot be made 0 for any value of %s satisfying the inequality, leading to relations like I < 0. ''' % (expr.subs(gen, _gen), _gen))) make_real = make_real.intersect(im_sol) empty = sol_sets = [S.EmptySet] start = domain.inf if valid(start) and start.is_finite: sol_sets.append(FiniteSet(start)) for x in reals: end = x if valid(_pt(start, end)): sol_sets.append(Interval(start, end, True, True)) if x in singularities: singularities.remove(x) else: if x in discontinuities: discontinuities.remove(x) _valid = valid(x) else: # it's a solution _valid = include_x if _valid: sol_sets.append(FiniteSet(x)) start = end end = domain.sup if valid(end) and end.is_finite: sol_sets.append(FiniteSet(end)) if valid(_pt(start, end)): sol_sets.append(Interval.open(start, end)) if im(expanded_e) != S.Zero and check: rv = (make_real).intersect(_domain) else: rv = Intersection((Union(*sol_sets)), make_real, _domain).subs(gen, _gen) return rv if not relational else rv.as_relational(_gen)
# need to use a function instead of lamda since hash of lambda changes on # each call to _pat_sincos def _integer_instance(n): return isinstance(n, Integer) @cacheit def _pat_sincos(x): a = Wild('a', exclude=[x]) n, m = [Wild(s, exclude=[x], properties=[_integer_instance]) for s in 'nm'] pat = sin(a * x)**n * cos(a * x)**m return pat, a, n, m _u = Dummy('u') def trigintegrate(f, x, conds='piecewise'): """Integrate f = Mul(trig) over x >>> from sympy import Symbol, sin, cos, tan, sec, csc, cot >>> from sympy.integrals.trigonometry import trigintegrate >>> from sympy.abc import x >>> trigintegrate(sin(x)*cos(x), x) sin(x)**2/2 >>> trigintegrate(sin(x)**2, x) x/2 - sin(x)*cos(x)/2
def test_Add_is_imaginary(): nn = Dummy(nonnegative=True) assert (I*nn + I).is_imaginary # issue 8046, 17
def trigsimp_groebner(expr, hints=[], quick=False, order="grlex", polynomial=False): """ Simplify trigonometric expressions using a groebner basis algorithm. This routine takes a fraction involving trigonometric or hyperbolic expressions, and tries to simplify it. The primary metric is the total degree. Some attempts are made to choose the simplest possible expression of the minimal degree, but this is non-rigorous, and also very slow (see the ``quick=True`` option). If ``polynomial`` is set to True, instead of simplifying numerator and denominator together, this function just brings numerator and denominator into a canonical form. This is much faster, but has potentially worse results. However, if the input is a polynomial, then the result is guaranteed to be an equivalent polynomial of minimal degree. The most important option is hints. Its entries can be any of the following: - a natural number - a function - an iterable of the form (func, var1, var2, ...) - anything else, interpreted as a generator A number is used to indicate that the search space should be increased. A function is used to indicate that said function is likely to occur in a simplified expression. An iterable is used indicate that func(var1 + var2 + ...) is likely to occur in a simplified . An additional generator also indicates that it is likely to occur. (See examples below). This routine carries out various computationally intensive algorithms. The option ``quick=True`` can be used to suppress one particularly slow step (at the expense of potentially more complicated results, but never at the expense of increased total degree). Examples ======== >>> from sympy.abc import x, y >>> from sympy import sin, tan, cos, sinh, cosh, tanh >>> from sympy.simplify.trigsimp import trigsimp_groebner Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens: >>> ex = sin(x)*cos(x) >>> trigsimp_groebner(ex) sin(x)*cos(x) This is because ``trigsimp_groebner`` only looks for a simplification involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try ``2*x`` by passing ``hints=[2]``: >>> trigsimp_groebner(ex, hints=[2]) sin(2*x)/2 >>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2]) -cos(2*x) Increasing the search space this way can quickly become expensive. A much faster way is to give a specific expression that is likely to occur: >>> trigsimp_groebner(ex, hints=[sin(2*x)]) sin(2*x)/2 Hyperbolic expressions are similarly supported: >>> trigsimp_groebner(sinh(2*x)/sinh(x)) 2*cosh(x) Note how no hints had to be passed, since the expression already involved ``2*x``. The tangent function is also supported. You can either pass ``tan`` in the hints, to indicate that tan should be tried whenever cosine or sine are, or you can pass a specific generator: >>> trigsimp_groebner(sin(x)/cos(x), hints=[tan]) tan(x) >>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)]) tanh(x) Finally, you can use the iterable form to suggest that angle sum formulae should be tried: >>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y)) >>> trigsimp_groebner(ex, hints=[(tan, x, y)]) tan(x + y) """ # TODO # - preprocess by replacing everything by funcs we can handle # - optionally use cot instead of tan # - more intelligent hinting. # For example, if the ideal is small, and we have sin(x), sin(y), # add sin(x + y) automatically... ? # - algebraic numbers ... # - expressions of lowest degree are not distinguished properly # e.g. 1 - sin(x)**2 # - we could try to order the generators intelligently, so as to influence # which monomials appear in the quotient basis # THEORY # ------ # Ratsimpmodprime above can be used to "simplify" a rational function # modulo a prime ideal. "Simplify" mainly means finding an equivalent # expression of lower total degree. # # We intend to use this to simplify trigonometric functions. To do that, # we need to decide (a) which ring to use, and (b) modulo which ideal to # simplify. In practice, (a) means settling on a list of "generators" # a, b, c, ..., such that the fraction we want to simplify is a rational # function in a, b, c, ..., with coefficients in ZZ (integers). # (2) means that we have to decide what relations to impose on the # generators. There are two practical problems: # (1) The ideal has to be *prime* (a technical term). # (2) The relations have to be polynomials in the generators. # # We typically have two kinds of generators: # - trigonometric expressions, like sin(x), cos(5*x), etc # - "everything else", like gamma(x), pi, etc. # # Since this function is trigsimp, we will concentrate on what to do with # trigonometric expressions. We can also simplify hyperbolic expressions, # but the extensions should be clear. # # One crucial point is that all *other* generators really should behave # like indeterminates. In particular if (say) "I" is one of them, then # in fact I**2 + 1 = 0 and we may and will compute non-sensical # expressions. However, we can work with a dummy and add the relation # I**2 + 1 = 0 to our ideal, then substitute back in the end. # # Now regarding trigonometric generators. We split them into groups, # according to the argument of the trigonometric functions. We want to # organise this in such a way that most trigonometric identities apply in # the same group. For example, given sin(x), cos(2*x) and cos(y), we would # group as [sin(x), cos(2*x)] and [cos(y)]. # # Our prime ideal will be built in three steps: # (1) For each group, compute a "geometrically prime" ideal of relations. # Geometrically prime means that it generates a prime ideal in # CC[gens], not just ZZ[gens]. # (2) Take the union of all the generators of the ideals for all groups. # By the geometric primality condition, this is still prime. # (3) Add further inter-group relations which preserve primality. # # Step (1) works as follows. We will isolate common factors in the # argument, so that all our generators are of the form sin(n*x), cos(n*x) # or tan(n*x), with n an integer. Suppose first there are no tan terms. # The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since # X**2 + Y**2 - 1 is irreducible over CC. # Now, if we have a generator sin(n*x), than we can, using trig identities, # express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this # relation to the ideal, preserving geometric primality, since the quotient # ring is unchanged. # Thus we have treated all sin and cos terms. # For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0. # (This requires of course that we already have relations for cos(n*x) and # sin(n*x).) It is not obvious, but it seems that this preserves geometric # primality. # XXX A real proof would be nice. HELP! # Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of # CC[S, C, T]: # - it suffices to show that the projective closure in CP**3 is # irreducible # - using the half-angle substitutions, we can express sin(x), tan(x), # cos(x) as rational functions in tan(x/2) # - from this, we get a rational map from CP**1 to our curve # - this is a morphism, hence the curve is prime # # Step (2) is trivial. # # Step (3) works by adding selected relations of the form # sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is # preserved by the same argument as before. def parse_hints(hints): """Split hints into (n, funcs, iterables, gens).""" n = 1 funcs, iterables, gens = [], [], [] for e in hints: if isinstance(e, (SYMPY_INTS, Integer)): n = e elif isinstance(e, FunctionClass): funcs.append(e) elif iterable(e): iterables.append((e[0], e[1:])) # XXX sin(x+2y)? # Note: we go through polys so e.g. # sin(-x) -> -sin(x) -> sin(x) gens.extend( parallel_poly_from_expr([e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens) else: gens.append(e) return n, funcs, iterables, gens def build_ideal(x, terms): """ Build generators for our ideal. Terms is an iterable with elements of the form (fn, coeff), indicating that we have a generator fn(coeff*x). If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed to appear in terms. Similarly for hyperbolic functions. For tan(n*x), sin(n*x) and cos(n*x) are guaranteed. """ I = [] y = Dummy('y') for fn, coeff in terms: for c, s, t, rel in ([cos, sin, tan, cos(x)**2 + sin(x)**2 - 1], [ cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1 ]): if coeff == 1 and fn in [c, s]: I.append(rel) elif fn == t: I.append(t(coeff * x) * c(coeff * x) - s(coeff * x)) elif fn in [c, s]: cn = fn(coeff * y).expand(trig=True).subs(y, x) I.append(fn(coeff * x) - cn) return list(set(I)) def analyse_gens(gens, hints): """ Analyse the generators ``gens``, using the hints ``hints``. The meaning of ``hints`` is described in the main docstring. Return a new list of generators, and also the ideal we should work with. """ # First parse the hints n, funcs, iterables, extragens = parse_hints(hints) debug('n=%s' % n, 'funcs:', funcs, 'iterables:', iterables, 'extragens:', extragens) # We just add the extragens to gens and analyse them as before gens = list(gens) gens.extend(extragens) # remove duplicates funcs = list(set(funcs)) iterables = list(set(iterables)) gens = list(set(gens)) # all the functions we can do anything with allfuncs = {sin, cos, tan, sinh, cosh, tanh} # sin(3*x) -> ((3, x), sin) trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens if g.func in allfuncs] # Our list of new generators - start with anything that we cannot # work with (i.e. is not a trigonometric term) freegens = [g for g in gens if g.func not in allfuncs] newgens = [] trigdict = {} for (coeff, var), fn in trigterms: trigdict.setdefault(var, []).append((coeff, fn)) res = [] # the ideal for key, val in trigdict.items(): # We have now assembeled a dictionary. Its keys are common # arguments in trigonometric expressions, and values are lists of # pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we # need to deal with fn(coeff*x0). We take the rational gcd of the # coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol", # all other arguments are integral multiples thereof. # We will build an ideal which works with sin(x), cos(x). # If hint tan is provided, also work with tan(x). Moreover, if # n > 1, also work with sin(k*x) for k <= n, and similarly for cos # (and tan if the hint is provided). Finally, any generators which # the ideal does not work with but we need to accommodate (either # because it was in expr or because it was provided as a hint) # we also build into the ideal. # This selection process is expressed in the list ``terms``. # build_ideal then generates the actual relations in our ideal, # from this list. fns = [x[1] for x in val] val = [x[0] for x in val] gcd = reduce(igcd, val) terms = [(fn, v / gcd) for (fn, v) in zip(fns, val)] fs = set(funcs + fns) for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]): if any(x in fs for x in (c, s, t)): fs.add(c) fs.add(s) for fn in fs: for k in range(1, n + 1): terms.append((fn, k)) extra = [] for fn, v in terms: if fn == tan: extra.append((sin, v)) extra.append((cos, v)) if fn in [sin, cos] and tan in fs: extra.append((tan, v)) if fn == tanh: extra.append((sinh, v)) extra.append((cosh, v)) if fn in [sinh, cosh] and tanh in fs: extra.append((tanh, v)) terms.extend(extra) x = gcd * Mul(*key) r = build_ideal(x, terms) res.extend(r) newgens.extend(set(fn(v * x) for fn, v in terms)) # Add generators for compound expressions from iterables for fn, args in iterables: if fn == tan: # Tan expressions are recovered from sin and cos. iterables.extend([(sin, args), (cos, args)]) elif fn == tanh: # Tanh expressions are recovered from sihn and cosh. iterables.extend([(sinh, args), (cosh, args)]) else: dummys = symbols('d:%i' % len(args), cls=Dummy) expr = fn(Add(*dummys)).expand(trig=True).subs( list(zip(dummys, args))) res.append(fn(Add(*args)) - expr) if myI in gens: res.append(myI**2 + 1) freegens.remove(myI) newgens.append(myI) return res, freegens, newgens myI = Dummy('I') expr = expr.subs(S.ImaginaryUnit, myI) subs = [(myI, S.ImaginaryUnit)] num, denom = cancel(expr).as_numer_denom() try: (pnum, pdenom), opt = parallel_poly_from_expr([num, denom]) except PolificationFailed: return expr debug('initial gens:', opt.gens) ideal, freegens, gens = analyse_gens(opt.gens, hints) debug('ideal:', ideal) debug('new gens:', gens, " -- len", len(gens)) debug('free gens:', freegens, " -- len", len(gens)) # NOTE we force the domain to be ZZ to stop polys from injecting generators # (which is usually a sign of a bug in the way we build the ideal) if not gens: return expr G = groebner(ideal, order=order, gens=gens, domain=ZZ) debug('groebner basis:', list(G), " -- len", len(G)) # If our fraction is a polynomial in the free generators, simplify all # coefficients separately: from sympy.simplify.ratsimp import ratsimpmodprime if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)): num = Poly(num, gens=gens + freegens).eject(*gens) res = [] for monom, coeff in num.terms(): ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens) # We compute the transitive closure of all generators that can # be reached from our generators through relations in the ideal. changed = True while changed: changed = False for p in ideal: p = Poly(p) if not ourgens.issuperset(p.gens) and \ not p.has_only_gens(*set(p.gens).difference(ourgens)): changed = True ourgens.update(p.exclude().gens) # NOTE preserve order! realgens = [x for x in gens if x in ourgens] # The generators of the ideal have now been (implicitly) split # into two groups: those involving ourgens and those that don't. # Since we took the transitive closure above, these two groups # live in subgrings generated by a *disjoint* set of variables. # Any sensible groebner basis algorithm will preserve this disjoint # structure (i.e. the elements of the groebner basis can be split # similarly), and and the two subsets of the groebner basis then # form groebner bases by themselves. (For the smaller generating # sets, of course.) ourG = [ g.as_expr() for g in G.polys if g.has_only_gens(*ourgens.intersection(g.gens)) ] res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \ ratsimpmodprime(coeff/denom, ourG, order=order, gens=realgens, quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)) return Add(*res) # NOTE The following is simpler and has less assumptions on the # groebner basis algorithm. If the above turns out to be broken, # use this. return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \ ratsimpmodprime(coeff/denom, list(G), order=order, gens=gens, quick=quick, domain=ZZ) for monom, coeff in num.terms()]) else: return ratsimpmodprime(expr, list(G), order=order, gens=freegens + gens, quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)
def __getitem__(self, key): if not key in self: self[key] = Dummy() return dict.__getitem__(self, key)
def reduce_inequalities(inequalities, symbols=[]): """Reduce a system of inequalities with rational coefficients. Examples ======== >>> from sympy import sympify as S, Symbol >>> from sympy.abc import x, y >>> from sympy.solvers.inequalities import reduce_inequalities >>> reduce_inequalities(0 <= x + 3, []) (-3 <= x) & (x < oo) >>> reduce_inequalities(0 <= x + y*2 - 1, [x]) (x < oo) & (x >= 1 - 2*y) """ if not iterable(inequalities): inequalities = [inequalities] inequalities = [sympify(i) for i in inequalities] gens = set().union(*[i.free_symbols for i in inequalities]) if not iterable(symbols): symbols = [symbols] symbols = (set(symbols) or gens) & gens if any(i.is_extended_real is False for i in symbols): raise TypeError( filldedent(''' inequalities cannot contain symbols that are not real. ''')) # make vanilla symbol real recast = { i: Dummy(i.name, extended_real=True) for i in gens if i.is_extended_real is None } inequalities = [i.xreplace(recast) for i in inequalities] symbols = {i.xreplace(recast) for i in symbols} # prefilter keep = [] for i in inequalities: if isinstance(i, Relational): i = i.func(i.lhs.as_expr() - i.rhs.as_expr(), 0) elif i not in (True, False): i = Eq(i, 0) if i == True: continue elif i == False: return S.false if i.lhs.is_number: raise NotImplementedError("could not determine truth value of %s" % i) keep.append(i) inequalities = keep del keep # solve system rv = _reduce_inequalities(inequalities, symbols) # restore original symbols and return return rv.xreplace({v: k for k, v in recast.items()})
def test_issue_8075(): raises(InconsistentAssumptions, lambda: Dummy(zero=True, finite=False)) raises(InconsistentAssumptions, lambda: Dummy(zero=True, infinite=True))
def euler_maclaurin(self, m=0, n=0, eps=0, eval_integral=True): """ Return an Euler-Maclaurin approximation of self, where m is the number of leading terms to sum directly and n is the number of terms in the tail. With m = n = 0, this is simply the corresponding integral plus a first-order endpoint correction. Returns (s, e) where s is the Euler-Maclaurin approximation and e is the estimated error (taken to be the magnitude of the first omitted term in the tail): >>> from sympy.abc import k, a, b >>> from sympy import Sum >>> Sum(1/k, (k, 2, 5)).doit().evalf() 1.28333333333333 >>> s, e = Sum(1/k, (k, 2, 5)).euler_maclaurin() >>> s -log(2) + 7/20 + log(5) >>> from sympy import sstr >>> print sstr((s.evalf(), e.evalf()), full_prec=True) (1.26629073187415, 0.0175000000000000) The endpoints may be symbolic: >>> s, e = Sum(1/k, (k, a, b)).euler_maclaurin() >>> s -log(a) + log(b) + 1/(2*b) + 1/(2*a) >>> e Abs(-1/(12*b**2) + 1/(12*a**2)) If the function is a polynomial of degree at most 2n+1, the Euler-Maclaurin formula becomes exact (and e = 0 is returned): >>> Sum(k, (k, 2, b)).euler_maclaurin() (b**2/2 + b/2 - 1, 0) >>> Sum(k, (k, 2, b)).doit() b**2/2 + b/2 - 1 With a nonzero eps specified, the summation is ended as soon as the remainder term is less than the epsilon. """ m = int(m) n = int(n) f = self.function assert len(self.limits) == 1 i, a, b = self.limits[0] s = S.Zero if m: for k in range(m): term = f.subs(i, a + k) if (eps and term and abs(term.evalf(3)) < eps): return s, abs(term) s += term a += m x = Dummy('x') I = C.Integral(f.subs(i, x), (x, a, b)) if eval_integral: I = I.doit() s += I def fpoint(expr): if b is S.Infinity: return expr.subs(i, a), 0 return expr.subs(i, a), expr.subs(i, b) fa, fb = fpoint(f) iterm = (fa + fb) / 2 g = f.diff(i) for k in xrange(1, n + 2): ga, gb = fpoint(g) term = C.bernoulli(2 * k) / C.factorial(2 * k) * (gb - ga) if (eps and term and abs(term.evalf(3)) < eps) or (k > n): break s += term g = g.diff(i, 2) return s + iterm, abs(term)