def _eval_subs(self, old, new): if old in self.variables: newexpr = self.expr.subs(old, new) i = self.variables.index(old) newvars = list(self.variables) newpt = list(self.point) if new.is_Symbol: newvars[i] = new else: syms = new.free_symbols if len(syms) == 1 or old in syms: if old in syms: var = self.variables[i] else: var = syms.pop() # First, try to substitute self.point in the "new" # expr to see if this is a fixed point. # E.g. O(y).subs(y, sin(x)) point = new.subs(var, self.point[i]) if point != self.point[i]: from sympy.solvers import solve d = Dummy() res = solve(old - new.subs(var, d), d, dict=True) point = d.subs(res[0]).limit(old, self.point[i]) newvars[i] = var newpt[i] = point elif old not in syms: del newvars[i], newpt[i] if not syms and new == self.point[i]: newvars.extend(syms) newpt.extend([S.Zero]*len(syms)) else: return return Order(newexpr, *zip(newvars, newpt))
def _eval_subs(self, old, new): if old in self.variables: newexpr = self.expr.subs(old, new) i = self.variables.index(old) newvars = list(self.variables) newpt = list(self.point) if new.is_symbol: newvars[i] = new else: syms = new.free_symbols if len(syms) == 1 or old in syms: if old in syms: var = self.variables[i] else: var = syms.pop() # First, try to substitute self.point in the "new" # expr to see if this is a fixed point. # E.g. O(y).subs(y, sin(x)) point = new.subs(var, self.point[i]) if point != self.point[i]: from sympy.solvers.solveset import solveset d = Dummy() sol = solveset(old - new.subs(var, d), d) if isinstance(sol, Complement): e1 = sol.args[0] e2 = sol.args[1] sol = set(e1) - set(e2) res = [dict(zip((d, ), sol))] point = d.subs(res[0]).limit(old, self.point[i]) newvars[i] = var newpt[i] = point elif old not in syms: del newvars[i], newpt[i] if not syms and new == self.point[i]: newvars.extend(syms) newpt.extend([S.Zero]*len(syms)) else: return return Order(newexpr, *zip(newvars, newpt))
def __getitem__(self, key): if not key in self: self[key] = Dummy() return dict.__getitem__(self, key)
def test_issue_9115(): n = Dummy('n', integer=True, nonnegative=True) assert (factorial(n) >= 1) == True assert (factorial(n) < 1) == False
def test_issue_7993(): x = Dummy(integer=True) y = Dummy(noninteger=True) assert (x - y).is_zero is False
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 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)
hyperexponential, and hypertangent cases, respectively. If case is 'auto', it will attempt to determine the type of the derivation automatically. See also ======== is_log_deriv_k_t_radical, is_deriv_k """ 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. return None
def _invert_real(f, g_ys, symbol): """Helper function for _invert.""" if f == symbol: return (f, g_ys) n = Dummy('n', real=True) if hasattr(f, 'inverse') and not isinstance(f, ( TrigonometricFunction, 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): pos = Interval(0, S.Infinity) neg = Interval(S.NegativeInfinity, 0) return _invert_real( f.args[0], Union( imageset(Lambda(n, n), g_ys).intersect(pos), imageset(Lambda(n, -n), g_ys).intersect(neg)), symbol) if f.is_Add: # f = g + h g, h = f.as_independent(symbol) if g is not 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 is not 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, TrigonometricFunction): if isinstance(g_ys, FiniteSet): def inv(trig): if isinstance(f, (sin, csc)): F = asin if isinstance(f, sin) else acsc return (lambda a: n * pi + (-1)**n * F(a), ) if isinstance(f, (cos, sec)): F = acos if isinstance(f, cos) else asec return ( lambda a: 2 * n * pi + F(a), lambda a: 2 * n * pi - F(a), ) if isinstance(f, (tan, cot)): return (lambda a: n * pi + f.inverse()(a), ) n = Dummy('n', integer=True) invs = S.EmptySet for L in inv(f): invs += Union( *[imageset(Lambda(n, L(g)), S.Integers) for g in g_ys]) return _invert_real(f.args[0], invs, symbol) return (f, g_ys)
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 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. """ from sympy.functions.elementary.complexes import im, re 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) and not c.has(im, re)): 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.negated.canonical in current_cond): continue nonredundant.append(c) cond = cond.func(*nonredundant) elif isinstance(cond, Relational): if cond.negated.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: newargs[-1] = ExprCondPair(expr, 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 gauss_laguerre(n, n_digits): r""" Computes the Gauss-Laguerre quadrature [1]_ points and weights. The Gauss-Laguerre quadrature approximates the integral: .. math:: \int_0^{\infty} e^{-x} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) The nodes `x_i` of an order `n` quadrature rule are the roots of `L_n` and the weights `w_i` are given by: .. math:: w_i = \frac{x_i}{(n+1)^2 \left(L_{n+1}(x_i)\right)^2} Parameters ========== n : the order of quadrature n_digits : number of significant digits of the points and weights to return Returns ======= (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. The points `x_i` and weights `w_i` are returned as ``(x, w)`` tuple of lists. Examples ======== >>> from sympy.integrals.quadrature import gauss_laguerre >>> x, w = gauss_laguerre(3, 5) >>> x [0.41577, 2.2943, 6.2899] >>> w [0.71109, 0.27852, 0.010389] >>> x, w = gauss_laguerre(6, 5) >>> x [0.22285, 1.1889, 2.9927, 5.7751, 9.8375, 15.983] >>> w [0.45896, 0.417, 0.11337, 0.010399, 0.00026102, 8.9855e-7] See Also ======== gauss_legendre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto References ========== .. [1] http://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature .. [2] http://people.sc.fsu.edu/~jburkardt/cpp_src/laguerre_rule/laguerre_rule.html """ x = Dummy("x") p = laguerre_poly(n, x, polys=True) p1 = laguerre_poly(n + 1, x, polys=True) xi = [] w = [] for r in p.real_roots(): if isinstance(r, RootOf): r = r.eval_rational(S(1) / 10**(n_digits + 2)) xi.append(r.n(n_digits)) w.append((r / ((n + 1)**2 * p1.subs(x, r)**2)).n(n_digits)) return xi, w
def gauss_lobatto(n, n_digits): r""" Computes the Gauss-Lobatto quadrature [1]_ points and weights. The Gauss-Lobatto quadrature approximates the integral: .. math:: \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) The nodes `x_i` of an order `n` quadrature rule are the roots of `P'_(n-1)` and the weights `w_i` are given by: .. math:: &w_i = \frac{2}{n(n-1) \left[P_{n-1}(x_i)\right]^2},\quad x\neq\pm 1\\ &w_i = \frac{2}{n(n-1)},\quad x=\pm 1 Parameters ========== n : the order of quadrature n_digits : number of significant digits of the points and weights to return Returns ======= (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. The points `x_i` and weights `w_i` are returned as ``(x, w)`` tuple of lists. Examples ======== >>> from sympy.integrals.quadrature import gauss_lobatto >>> x, w = gauss_lobatto(3, 5) >>> x [-1, 0, 1] >>> w [0.33333, 1.3333, 0.33333] >>> x, w = gauss_lobatto(4, 5) >>> x [-1, -0.44721, 0.44721, 1] >>> w [0.16667, 0.83333, 0.83333, 0.16667] See Also ======== gauss_legendre,gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi References ========== .. [1] http://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Lobatto_rules .. [2] http://people.math.sfu.ca/~cbm/aands/page_888.htm """ x = Dummy("x") p = legendre_poly(n - 1, x, polys=True) pd = p.diff(x) xi = [] w = [] for r in pd.real_roots(): if isinstance(r, RootOf): r = r.eval_rational(S(1) / 10**(n_digits + 2)) xi.append(r.n(n_digits)) w.append((2 / (n * (n - 1) * p.subs(x, r)**2)).n(n_digits)) xi.insert(0, -1) xi.append(1) w.insert(0, (S(2) / (n * (n - 1))).n(n_digits)) w.append((S(2) / (n * (n - 1))).n(n_digits)) return xi, w
def gauss_jacobi(n, alpha, beta, n_digits): r""" Computes the Gauss-Jacobi quadrature [1]_ points and weights. The Gauss-Jacobi quadrature of the first kind approximates the integral: .. math:: \int_{-1}^1 (1-x)^\alpha (1+x)^\beta f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) The nodes `x_i` of an order `n` quadrature rule are the roots of `P^{(\alpha,\beta)}_n` and the weights `w_i` are given by: .. math:: w_i = -\frac{2n+\alpha+\beta+2}{n+\alpha+\beta+1} \frac{\Gamma(n+\alpha+1)\Gamma(n+\beta+1)} {\Gamma(n+\alpha+\beta+1)(n+1)!} \frac{2^{\alpha+\beta}}{P'_n(x_i) P^{(\alpha,\beta)}_{n+1}(x_i)} Parameters ========== n : the order of quadrature alpha : the first parameter of the Jacobi Polynomial, `\alpha > -1` beta : the second parameter of the Jacobi Polynomial, `\beta > -1` n_digits : number of significant digits of the points and weights to return Returns ======= (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. The points `x_i` and weights `w_i` are returned as ``(x, w)`` tuple of lists. Examples ======== >>> from sympy import S >>> from sympy.integrals.quadrature import gauss_jacobi >>> x, w = gauss_jacobi(3, S.Half, -S.Half, 5) >>> x [-0.90097, -0.22252, 0.62349] >>> w [1.7063, 1.0973, 0.33795] >>> x, w = gauss_jacobi(6, 1, 1, 5) >>> x [-0.87174, -0.5917, -0.2093, 0.2093, 0.5917, 0.87174] >>> w [0.050584, 0.22169, 0.39439, 0.39439, 0.22169, 0.050584] See Also ======== gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, gauss_chebyshev_t, gauss_chebyshev_u, gauss_lobatto References ========== .. [1] http://en.wikipedia.org/wiki/Gauss%E2%80%93Jacobi_quadrature .. [2] http://people.sc.fsu.edu/~jburkardt/cpp_src/jacobi_rule/jacobi_rule.html .. [3] http://people.sc.fsu.edu/~jburkardt/cpp_src/gegenbauer_rule/gegenbauer_rule.html """ x = Dummy("x") p = jacobi_poly(n, alpha, beta, x, polys=True) pd = p.diff(x) pn = jacobi_poly(n + 1, alpha, beta, x, polys=True) xi = [] w = [] for r in p.real_roots(): if isinstance(r, RootOf): r = r.eval_rational(S(1) / 10**(n_digits + 2)) xi.append(r.n(n_digits)) w.append( (-(2 * n + alpha + beta + 2) / (n + alpha + beta + S.One) * (gamma(n + alpha + 1) * gamma(n + beta + 1)) / (gamma(n + alpha + beta + S.One) * gamma(n + 2)) * 2**(alpha + beta) / (pd.subs(x, r) * pn.subs(x, r))).n(n_digits)) return xi, w
def gauss_gen_laguerre(n, alpha, n_digits): r""" Computes the generalized Gauss-Laguerre quadrature [1]_ points and weights. The generalized Gauss-Laguerre quadrature approximates the integral: .. math:: \int_{0}^\infty x^{\alpha} e^{-x} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) The nodes `x_i` of an order `n` quadrature rule are the roots of `L^{\alpha}_n` and the weights `w_i` are given by: .. math:: w_i = \frac{\Gamma(\alpha+n)} {n \Gamma(n) L^{\alpha}_{n-1}(x_i) L^{\alpha+1}_{n-1}(x_i)} Parameters ========== n : the order of quadrature alpha : the exponent of the singularity, `\alpha > -1` n_digits : number of significant digits of the points and weights to return Returns ======= (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. The points `x_i` and weights `w_i` are returned as ``(x, w)`` tuple of lists. Examples ======== >>> from sympy import S >>> from sympy.integrals.quadrature import gauss_gen_laguerre >>> x, w = gauss_gen_laguerre(3, -S.Half, 5) >>> x [0.19016, 1.7845, 5.5253] >>> w [1.4493, 0.31413, 0.00906] >>> x, w = gauss_gen_laguerre(4, 3*S.Half, 5) >>> x [0.97851, 2.9904, 6.3193, 11.712] >>> w [0.53087, 0.67721, 0.11895, 0.0023152] See Also ======== gauss_legendre, gauss_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto References ========== .. [1] http://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature .. [2] http://people.sc.fsu.edu/~jburkardt/cpp_src/gen_laguerre_rule/gen_laguerre_rule.html """ x = Dummy("x") p = laguerre_poly(n, x, alpha=alpha, polys=True) p1 = laguerre_poly(n - 1, x, alpha=alpha, polys=True) p2 = laguerre_poly(n - 1, x, alpha=alpha + 1, polys=True) xi = [] w = [] for r in p.real_roots(): if isinstance(r, RootOf): r = r.eval_rational(S(1) / 10**(n_digits + 2)) xi.append(r.n(n_digits)) w.append((gamma(alpha + n) / (n * gamma(n) * p1.subs(x, r) * p2.subs(x, r))).n(n_digits)) return xi, w
def gauss_hermite(n, n_digits): r""" Computes the Gauss-Hermite quadrature [1]_ points and weights. The Gauss-Hermite quadrature approximates the integral: .. math:: \int_{-\infty}^{\infty} e^{-x^2} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) The nodes `x_i` of an order `n` quadrature rule are the roots of `H_n` and the weights `w_i` are given by: .. math:: w_i = \frac{2^{n-1} n! \sqrt{\pi}}{n^2 \left(H_{n-1}(x_i)\right)^2} Parameters ========== n : the order of quadrature n_digits : number of significant digits of the points and weights to return Returns ======= (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. The points `x_i` and weights `w_i` are returned as ``(x, w)`` tuple of lists. Examples ======== >>> from sympy.integrals.quadrature import gauss_hermite >>> x, w = gauss_hermite(3, 5) >>> x [-1.2247, 0, 1.2247] >>> w [0.29541, 1.1816, 0.29541] >>> x, w = gauss_hermite(6, 5) >>> x [-2.3506, -1.3358, -0.43608, 0.43608, 1.3358, 2.3506] >>> w [0.00453, 0.15707, 0.72463, 0.72463, 0.15707, 0.00453] See Also ======== gauss_legendre, gauss_laguerre, gauss_gen_laguerre, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto References ========== .. [1] http://en.wikipedia.org/wiki/Gauss-Hermite_Quadrature .. [2] http://people.sc.fsu.edu/~jburkardt/cpp_src/hermite_rule/hermite_rule.html .. [3] http://people.sc.fsu.edu/~jburkardt/cpp_src/gen_hermite_rule/gen_hermite_rule.html """ x = Dummy("x") p = hermite_poly(n, x, polys=True) p1 = hermite_poly(n - 1, x, polys=True) xi = [] w = [] for r in p.real_roots(): if isinstance(r, RootOf): r = r.eval_rational(S(1) / 10**(n_digits + 2)) xi.append(r.n(n_digits)) w.append(((2**(n - 1) * factorial(n) * sqrt(pi)) / (n**2 * p1.subs(x, r)**2)).n(n_digits)) return xi, w
# 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 _solve_as_poly(f, symbol, domain=S.Complexes): """ 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: result = ConditionSet(symbol, Eq(f, 0), domain) else: poly = Poly(f) if poly is None: result = ConditionSet(symbol, Eq(f, 0), domain) 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: result = ConditionSet(symbol, Eq(f, 0), domain) if gen != symbol: y = Dummy('y') inverter = invert_real if domain.is_subset( S.Reals) else invert_complex lhs, rhs_s = inverter(gen, y, symbol) if lhs == symbol: result = Union(*[rhs_s.subs(y, s) for s in poly_solns]) else: result = ConditionSet(symbol, Eq(f, 0), domain) else: result = ConditionSet(symbol, Eq(f, 0), domain) 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) if isinstance(result, FiniteSet): result = result.intersection(domain) return result else: return ConditionSet(symbol, Eq(f, 0), domain)
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 M, N = s[c1].as_numer_denom() 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, v = Qv 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): # TODO: We treat this as 'no solution', until the structure # theorem version of parametric_log_deriv is implemented. return None 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)
if force: eq, rep = posify(eq) return powdenest(eq, force=False).xreplace(rep) if polar: eq, rep = polarify(eq) return unpolarify(powdenest(unpolarify(eq, exponents_only=True)), rep) new = powsimp(sympify(eq)) return new.xreplace( Transform(_denest_pow, filter=lambda m: m.is_Pow or isinstance(m, exp))) _y = Dummy('y') def _denest_pow(eq): """ Denest powers. This is a helper function for powdenest that performs the actual transformation. """ from sympy.simplify.simplify import logcombine b, e = eq.as_base_exp() if b.is_Pow or isinstance(b.func, exp) and e != 1: new = b._eval_power(e) if new is not None:
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(isinstance(v, 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 = dict([(k, 1 / Dummy()) for k in variables]) rs = dict([(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 intersection(self, o): """ The intersection with other geometrical entity. Parameters ========== Point, Point3D, LinearEntity, LinearEntity3D, Plane Returns ======= List Examples ======== >>> from sympy import Point3D, 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))] """ 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 test_Add_is_imaginary(): nn = Dummy(nonnegative=True) assert (I * nn + I).is_imaginary # issue 8046, 17
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 :func:`sympy.solvers.solveset.solvify`. Notes ===== Currently, we cannot solve all the inequalities due to limitations in :func:`sympy.solvers.solveset.solvify`. Also, the solution returned for trigonometric inequalities are restricted in its periodic interval. See Also ======== sympy.solvers.solveset.solvify: solver returning solveset solutions with solve's output API Examples ======== >>> from sympy import solve_univariate_inequality, Symbol, sin, Interval, S >>> x = Symbol('x') >>> solve_univariate_inequality(x**2 >= 4, x) ((2 <= x) & (x < oo)) | ((-oo < x) & (x <= -2)) >>> 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.solvers.solvers import denoms if domain.is_subset(S.Reals) is False: raise NotImplementedError( filldedent(''' Inequalities in the complex domain are not supported. Try the real domain by setting domain=S.Reals''')) elif domain is not S.Reals: rv = solve_univariate_inequality( expr, gen, relational=False, continuous=continuous).intersection(domain) if relational: rv = rv.as_relational(gen) return rv else: pass # continue with attempt to solve in Real domain # 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_extended_real is False: rv = S.EmptySet return rv if not relational else rv.as_relational(_gen) elif gen.is_extended_real is None: gen = Dummy('gen', extended_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 == 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 in ('<', '<='): if expr.func(frange.sup, 0): rv = domain elif not expr.func(frange.inf, 0): rv = S.EmptySet elif rel in ('>', '>='): 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).intersect(_domain) _domain = domain 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_extended_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_extended_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_extended_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_extended_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 im_sol is S.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) sol_sets = [S.EmptySet] start = domain.inf if start in domain and 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 end in domain and 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)
def test_issue_8075(): raises(InconsistentAssumptions, lambda: Dummy(zero=True, finite=False)) raises(InconsistentAssumptions, lambda: Dummy(zero=True, infinite=True))
def reduce_inequalities(inequalities, symbols=[]): """Reduce a system of inequalities with rational coefficients. Examples ======== >>> from sympy.abc import x, y >>> from sympy 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_10024(): x = Dummy('x') assert Mod(x, 2 * pi).is_zero is None
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_Pow: base, exponent = arg.as_base_exp() if base.is_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_nonnegative: return base**re(exponent) if base.is_negative: return (-base)**re(exponent) * exp(-S.Pi * im(exponent)) return if isinstance(arg, exp): return exp(re(arg.args[0])) if isinstance(arg, AppliedUndef): 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_nonnegative: return arg if arg.is_nonpositive: return -arg if arg.is_imaginary: arg2 = -S.ImaginaryUnit * arg if arg2.is_nonnegative: return arg2 # reject result if all new conjugates are just wrappers around # an expression that was already in the arg conj = arg.conjugate() 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_real is None] if not unk or not all(conj.has(conjugate(u)) for u in unk): return sqrt(expand_mul(arg * conj))
def _eval_evalf(self, prec): """Evaluate this complex root to the given precision. """ with workprec(prec): g = self.poly.gen if not g.is_Symbol: d = Dummy('x') func = lambdify(d, self.expr.subs(g, d)) else: func = lambdify(g, self.expr) interval = self._get_interval() if not self.is_real: # For complex intervals, we need to keep refining until the # imaginary interval is disjunct with other roots, that is, # until both ends get refined. ay = interval.ay by = interval.by while interval.ay == ay or interval.by == by: interval = interval.refine() while True: if self.is_real: a = mpf(str(interval.a)) b = mpf(str(interval.b)) if a == b: root = a break x0 = mpf(str(interval.center)) else: ax = mpf(str(interval.ax)) bx = mpf(str(interval.bx)) ay = mpf(str(interval.ay)) by = mpf(str(interval.by)) if ax == bx and ay == by: # the sign of the imaginary part will be assigned # according to the desired index using the fact that # roots are sorted with negative imag parts coming # before positive (and all imag roots coming after real # roots) deg = self.poly.degree() i = self.index # a positive attribute after creation if (deg - i) % 2: if ay < 0: ay = -ay else: if ay > 0: ay = -ay root = mpc(ax, ay) break x0 = mpc(*map(str, interval.center)) try: root = findroot(func, x0) # If the (real or complex) root is not in the 'interval', # then keep refining the interval. This happens if findroot # accidentally finds a different root outside of this # interval because our initial estimate 'x0' was not close # enough. It is also possible that the secant method will # get trapped by a max/min in the interval; the root # verification by findroot will raise a ValueError in this # case and the interval will then be tightened -- and # eventually the root will be found. if self.is_real: if (a <= root <= b): break elif (ax <= root.real <= bx and ay <= root.imag <= by): break except ValueError: pass interval = interval.refine() return Float._new(root.real._mpf_, prec) + I * Float._new(root.imag._mpf_, prec)
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 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 gauss_legendre(n, n_digits): r""" Computes the Gauss-Legendre quadrature [1]_ points and weights. The Gauss-Legendre quadrature approximates the integral: .. math:: \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) The nodes `x_i` of an order `n` quadrature rule are the roots of `P_n` and the weights `w_i` are given by: .. math:: w_i = \frac{2}{\left(1-x_i^2\right) \left(P'_n(x_i)\right)^2} Parameters ========== n : the order of quadrature n_digits : number of significant digits of the points and weights to return Returns ======= (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. The points `x_i` and weights `w_i` are returned as ``(x, w)`` tuple of lists. Examples ======== >>> from sympy.integrals.quadrature import gauss_legendre >>> x, w = gauss_legendre(3, 5) >>> x [-0.7746, 0, 0.7746] >>> w [0.55556, 0.88889, 0.55556] >>> x, w = gauss_legendre(4, 5) >>> x [-0.86114, -0.33998, 0.33998, 0.86114] >>> w [0.34785, 0.65215, 0.65215, 0.34785] See Also ======== gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto References ========== .. [1] http://en.wikipedia.org/wiki/Gaussian_quadrature .. [2] http://people.sc.fsu.edu/~jburkardt/cpp_src/legendre_rule/legendre_rule.html """ x = Dummy("x") p = legendre_poly(n, x, polys=True) pd = p.diff(x) xi = [] w = [] for r in p.real_roots(): if isinstance(r, RootOf): r = r.eval_rational(S(1) / 10**(n_digits + 2)) xi.append(r.n(n_digits)) w.append((2 / ((1 - r**2) * pd.subs(x, r)**2)).n(n_digits)) return xi, w