def _lambert(eq, x): """ Given an expression assumed to be in the form ``F(X, a..f) = a*log(b*X + c) + d*X + f = 0`` where X = g(x) and x = g^-1(X), return the Lambert solution if possible: ``x = g^-1(-c/b + (a/d)*W(d/(a*b)*exp(c*d/a/b)*exp(-f/a)))``. """ eq = _mexpand(expand_log(eq)) mainlog = _mostfunc(eq, log, x) if not mainlog: return [] # violated assumptions other = eq.subs(mainlog, 0) if (-other).func is log: eq = (eq - other).subs(mainlog, mainlog.args[0]) mainlog = mainlog.args[0] if mainlog.func is not log: return [] # violated assumptions other = -(-other).args[0] eq += other if not x in other.free_symbols: return [] # violated assumptions d, f, X2 = _linab(other, x) logterm = collect(eq - other, mainlog) a = logterm.as_coefficient(mainlog) if a is None or x in a.free_symbols: return [] # violated assumptions logarg = mainlog.args[0] b, c, X1 = _linab(logarg, x) if X1 != X2: return [] # violated assumptions u = Dummy('rhs') sol = [] # check only real solutions: for k in [-1, 0]: l = LambertW(d/(a*b)*exp(c*d/a/b)*exp(-f/a), k) # if W's arg is between -1/e and 0 there is # a -1 branch real solution, too. if k and not l.is_real: continue rhs = -c/b + (a/d)*l solns = solve(X1 - u, x) for i, tmp in enumerate(solns): solns[i] = tmp.subs(u, rhs) sol.append(solns[i]) return sol
def _matches_commutative(self, expr, repl_dict={}, old=False): """ Matches Add/Mul "pattern" to an expression "expr". repl_dict ... a dictionary of (wild: expression) pairs, that get returned with the results This function is the main workhorse for Add/Mul. For instance: >>> from sympy import symbols, Wild, sin >>> a = Wild("a") >>> b = Wild("b") >>> c = Wild("c") >>> x, y, z = symbols("x y z") >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z) {a_: x, b_: y, c_: z} In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is the expression. The repl_dict contains parts that were already matched. For example here: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x}) {a_: x, b_: y, c_: z} the only function of the repl_dict is to return it in the result, e.g. if you omit it: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z) {b_: y, c_: z} the "a: x" is not returned in the result, but otherwise it is equivalent. """ # make sure expr is Expr if pattern is Expr from .expr import Add, Expr from sympy import Mul if isinstance(self, Expr) and not isinstance(expr, Expr): return None # handle simple patterns if self == expr: return repl_dict d = self._matches_simple(expr, repl_dict) if d is not None: return d # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2) from .function import WildFunction from .symbol import Wild wild_part = [] exact_part = [] for p in ordered(self.args): if p.has(Wild, WildFunction) and (not expr.has(p)): # not all Wild should stay Wilds, for example: # (w2+w3).matches(w1) -> (w1+w3).matches(w1) -> w3.matches(0) wild_part.append(p) else: exact_part.append(p) if exact_part: exact = self.func(*exact_part) free = expr.free_symbols if free and (exact.free_symbols - free): # there are symbols in the exact part that are not # in the expr; but if there are no free symbols, let # the matching continue return None newpattern = self.func(*wild_part) newexpr = self._combine_inverse(expr, exact) if not old and (expr.is_Add or expr.is_Mul): if newexpr.count_ops() > expr.count_ops(): return None return newpattern.matches(newexpr, repl_dict) # now to real work ;) i = 0 saw = set() while expr not in saw: saw.add(expr) expr_list = (self.identity,) + tuple(ordered(self.make_args(expr))) for last_op in reversed(expr_list): for w in reversed(wild_part): d1 = w.matches(last_op, repl_dict) if d1 is not None: d2 = self.xreplace(d1).matches(expr, d1) if d2 is not None: return d2 if i == 0: if self.is_Mul: # make e**i look like Mul if expr.is_Pow and expr.exp.is_Integer: if expr.exp > 0: expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False) else: expr = Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False) i += 1 continue elif self.is_Add: # make i*e look like Add c, e = expr.as_coeff_Mul() if abs(c) > 1: if c > 0: expr = Add(*[e, (c - 1)*e], evaluate=False) else: expr = Add(*[-e, (c + 1)*e], evaluate=False) i += 1 continue # try collection on non-Wild symbols from sympy.simplify.radsimp import collect was = expr did = set() for w in reversed(wild_part): c, w = w.as_coeff_mul(Wild) free = c.free_symbols - did if free: did.update(free) expr = collect(expr, free) if expr != was: i += 0 continue break # if we didn't continue, there is nothing more to do return
def _integrate(field=None): irreducibles = set() atans = set() pairs = set() for poly in reducibles: for z in poly.free_symbols: if z in V: break # should this be: `irreducibles |= \ else: # set(root_factors(poly, z, filter=field))` continue # and the line below deleted? # | # V irreducibles |= set(root_factors(poly, z, filter=field)) log_part, atan_part = [], [] for poly in list(irreducibles): m = collect(poly, I, evaluate=False) y = m.get(I, S.Zero) if y: x = m.get(S.One, S.Zero) if x.has(I) or y.has(I): continue # nontrivial x + I*y pairs.add((x, y)) irreducibles.remove(poly) while pairs: x, y = pairs.pop() if (x, -y) in pairs: pairs.remove((x, -y)) # Choosing b with no minus sign if y.could_extract_minus_sign(): y = -y irreducibles.add(x*x + y*y) atans.add(atan(x/y)) else: irreducibles.add(x + I*y) B = _symbols('B', len(irreducibles)) C = _symbols('C', len(atans)) # Note: the ordering matters here for poly, b in reversed(list(ordered(zip(irreducibles, B)))): if poly.has(*V): poly_coeffs.append(b) log_part.append(b * log(poly)) for poly, c in reversed(list(ordered(zip(atans, C)))): if poly.has(*V): poly_coeffs.append(c) atan_part.append(c * poly) # TODO: Currently it's better to use symbolic expressions here instead # of rational functions, because it's simpler and FracElement doesn't # give big speed improvement yet. This is because cancellation is slow # due to slow polynomial GCD algorithms. If this gets improved then # revise this code. candidate = poly_part/poly_denom + Add(*log_part) + Add(*atan_part) h = F - _derivation(candidate) / denom raw_numer = h.as_numer_denom()[0] # Rewrite raw_numer as a polynomial in K[coeffs][V] where K is a field # that we have to determine. We can't use simply atoms() because log(3), # sqrt(y) and similar expressions can appear, leading to non-trivial # domains. syms = set(poly_coeffs) | set(V) non_syms = set([]) def find_non_syms(expr): if expr.is_Integer or expr.is_Rational: pass # ignore trivial numbers elif expr in syms: pass # ignore variables elif not expr.has(*syms): non_syms.add(expr) elif expr.is_Add or expr.is_Mul or expr.is_Pow: list(map(find_non_syms, expr.args)) else: # TODO: Non-polynomial expression. This should have been # filtered out at an earlier stage. raise PolynomialError try: find_non_syms(raw_numer) except PolynomialError: return None else: ground, _ = construct_domain(non_syms, field=True) coeff_ring = PolyRing(poly_coeffs, ground) ring = PolyRing(V, coeff_ring) try: numer = ring.from_expr(raw_numer) except ValueError: raise PolynomialError solution = solve_lin_sys(numer.coeffs(), coeff_ring, _raw=False) if solution is None: return None else: return candidate.subs(solution).subs( list(zip(poly_coeffs, [S.Zero]*len(poly_coeffs))))
def classify_pde(eq, func=None, dict=False, **kwargs): """ Returns a tuple of possible pdsolve() classifications for a PDE. The tuple is ordered so that first item is the classification that pdsolve() uses to solve the PDE by default. In general, classifications near the beginning of the list will produce better solutions faster than those near the end, though there are always exceptions. To make pdsolve use a different classification, use pdsolve(PDE, func, hint=<classification>). See also the pdsolve() docstring for different meta-hints you can use. If ``dict`` is true, classify_pde() will return a dictionary of hint:match expression terms. This is intended for internal use by pdsolve(). Note that because dictionaries are ordered arbitrarily, this will most likely not be in the same order as the tuple. You can get help on different hints by doing help(pde.pde_hintname), where hintname is the name of the hint without "_Integral". See sympy.pde.allhints or the sympy.pde docstring for a list of all supported hints that can be returned from classify_pde. Examples ======== >>> from sympy.solvers.pde import classify_pde >>> from sympy import Function, diff, Eq >>> from sympy.abc import x, y >>> f = Function('f') >>> u = f(x, y) >>> ux = u.diff(x) >>> uy = u.diff(y) >>> eq = Eq(1 + (2*(ux/u)) + (3*(uy/u))) >>> classify_pde(eq) ('1st_linear_constant_coeff_homogeneous',) """ prep = kwargs.pop('prep', True) if func and len(func.args) != 2: raise NotImplementedError("Right now only partial " "differential equations of two variables are supported") if prep or func is None: prep, func_ = _preprocess(eq, func) if func is None: func = func_ if isinstance(eq, Equality): if eq.rhs != 0: return classify_pde(eq.lhs - eq.rhs, func) eq = eq.lhs f = func.func x = func.args[0] y = func.args[1] fx = f(x,y).diff(x) fy = f(x,y).diff(y) # TODO : For now pde.py uses support offered by the ode_order function # to find the order with respect to a multi-variable function. An # improvement could be to classify the order of the PDE on the basis of # individual variables. order = ode_order(eq, f(x,y)) # hint:matchdict or hint:(tuple of matchdicts) # Also will contain "default":<default hint> and "order":order items. matching_hints = {'order': order} if not order: if dict: matching_hints["default"] = None return matching_hints else: return () eq = expand(eq) a = Wild('a', exclude = [f(x,y)]) b = Wild('b', exclude = [f(x,y), fx, fy, x, y]) c = Wild('c', exclude = [f(x,y), fx, fy, x, y]) d = Wild('d', exclude = [f(x,y), fx, fy, x, y]) e = Wild('e', exclude = [f(x,y), fx, fy]) n = Wild('n', exclude = [x, y]) # Try removing the smallest power of f(x,y) # from the highest partial derivatives of f(x,y) reduced_eq = None if eq.is_Add: var = set(combinations_with_replacement((x,y), order)) dummyvar = var.copy() power = None for i in var: coeff = eq.coeff(f(x,y).diff(*i)) if coeff != 1: match = coeff.match(a*f(x,y)**n) if match and match[a]: power = match[n] dummyvar.remove(i) break dummyvar.remove(i) for i in dummyvar: coeff = eq.coeff(f(x,y).diff(*i)) if coeff != 1: match = coeff.match(a*f(x,y)**n) if match and match[a] and match[n] < power: power = match[n] if power: den = f(x,y)**power reduced_eq = Add(*[arg/den for arg in eq.args]) if not reduced_eq: reduced_eq = eq if order == 1: reduced_eq = collect(reduced_eq, f(x, y)) r = reduced_eq.match(b*fx + c*fy + d*f(x,y) + e) if r: if not r[e]: ## Linear first-order homogeneous partial-differential ## equation with constant coefficients r.update({'b': b, 'c': c, 'd': d}) matching_hints["1st_linear_constant_coeff_homogeneous"] = r else: if r[b]**2 + r[c]**2 != 0: ## Linear first-order general partial-differential ## equation with constant coefficients r.update({'b': b, 'c': c, 'd': d, 'e': e}) matching_hints["1st_linear_constant_coeff"] = r matching_hints[ "1st_linear_constant_coeff_Integral"] = r else: b = Wild('b', exclude=[f(x, y), fx, fy]) c = Wild('c', exclude=[f(x, y), fx, fy]) d = Wild('d', exclude=[f(x, y), fx, fy]) r = reduced_eq.match(b*fx + c*fy + d*f(x,y) + e) if r: r.update({'b': b, 'c': c, 'd': d, 'e': e}) matching_hints["1st_linear_variable_coeff"] = r # Order keys based on allhints. retlist = [] for i in allhints: if i in matching_hints: retlist.append(i) if dict: # Dictionaries are ordered arbitrarily, so make note of which # hint would come first for pdsolve(). Use an ordered dict in Py 3. matching_hints["default"] = None matching_hints["ordered_hints"] = tuple(retlist) for i in allhints: if i in matching_hints: matching_hints["default"] = i break return matching_hints else: return tuple(retlist)
def _solve_lambert(f, symbol, gens): """Return solution to ``f`` if it is a Lambert-type expression else raise NotImplementedError. The equality, ``f(x, a..f) = a*log(b*X + c) + d*X - f = 0`` has the solution, `X = -c/b + (a/d)*W(d/(a*b)*exp(c*d/a/b)*exp(f/a))`. There are a variety of forms for `f(X, a..f)` as enumerated below: 1a1) if B**B = R for R not [0, 1] then log(B) + log(log(B)) = log(log(R)) X = log(B), a = 1, b = 1, c = 0, d = 1, f = log(log(R)) 1a2) if B*(b*log(B) + c)**a = R then log(B) + a*log(b*log(B) + c) = log(R) X = log(B); d=1, f=log(R) 1b) if a*log(b*B + c) + d*B = R then X = B, f = R 2a) if (b*B + c)*exp(d*B + g) = R then log(b*B + c) + d*B + g = log(R) a = 1, f = log(R) - g, X = B 2b) if -b*B + g*exp(d*B + h) = c then log(g) + d*B + h - log(b*B + c) = 0 a = -1, f = -h - log(g), X = B 3) if d*p**(a*B + g) - b*B = c then log(d) + (a*B + g)*log(p) - log(c + b*B) = 0 a = -1, d = a*log(p), f = -log(d) - g*log(p) """ nrhs, lhs = f.as_independent(symbol, as_Add=True) rhs = -nrhs lamcheck = [tmp for tmp in gens if (tmp.func in [exp, log] or (tmp.is_Pow and symbol in tmp.exp.free_symbols))] if not lamcheck: raise NotImplementedError() if lhs.is_Mul: lhs = expand_log(log(lhs)) rhs = log(rhs) lhs = factor(lhs, deep=True) # make sure we are inverted as completely as possible r = Dummy() i, lhs = _invert(lhs - r, symbol) rhs = i.xreplace({r: rhs}) # For the first ones: # 1a1) B**B = R != 0 (when 0, there is only a solution if the base is 0, # but if it is, the exp is 0 and 0**0=1 # comes back as B*log(B) = log(R) # 1a2) B*(a + b*log(B))**p = R or with monomial expanded or with whole # thing expanded comes back unchanged # log(B) + p*log(a + b*log(B)) = log(R) # lhs is Mul: # expand log of both sides to give: # log(B) + log(log(B)) = log(log(R)) # 1b) d*log(a*B + b) + c*B = R # lhs is Add: # isolate c*B and expand log of both sides: # log(c) + log(B) = log(R - d*log(a*B + b)) soln = [] if not soln: mainlog = _mostfunc(lhs, log, symbol) if mainlog: if lhs.is_Mul and rhs != 0: soln = _lambert(log(lhs) - log(rhs), symbol) elif lhs.is_Add: other = lhs.subs(mainlog, 0) if other and not other.is_Add and [ tmp for tmp in other.atoms(Pow) if symbol in tmp.free_symbols]: if not rhs: diff = log(other) - log(other - lhs) else: diff = log(lhs - other) - log(rhs - other) soln = _lambert(expand_log(diff), symbol) else: #it's ready to go soln = _lambert(lhs - rhs, symbol) # For the next two, # collect on main exp # 2a) (b*B + c)*exp(d*B + g) = R # lhs is mul: # log to give # log(b*B + c) + d*B = log(R) - g # 2b) -b*B + g*exp(d*B + h) = R # lhs is add: # add b*B # log and rearrange # log(R + b*B) - d*B = log(g) + h if not soln: mainexp = _mostfunc(lhs, exp, symbol) if mainexp: lhs = collect(lhs, mainexp) if lhs.is_Mul and rhs != 0: soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol) elif lhs.is_Add: # move all but mainexp-containing term to rhs other = lhs.subs(mainexp, 0) mainterm = lhs - other rhs = rhs - other if (mainterm.could_extract_minus_sign() and rhs.could_extract_minus_sign()): mainterm *= -1 rhs *= -1 diff = log(mainterm) - log(rhs) soln = _lambert(expand_log(diff), symbol) # 3) d*p**(a*B + b) + c*B = R # collect on main pow # log(R - c*B) - a*B*log(p) = log(d) + b*log(p) if not soln: mainpow = _mostfunc(lhs, Pow, symbol) if mainpow and symbol in mainpow.exp.free_symbols: lhs = collect(lhs, mainpow) if lhs.is_Mul and rhs != 0: soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol) elif lhs.is_Add: # move all but mainpow-containing term to rhs other = lhs.subs(mainpow, 0) mainterm = lhs - other rhs = rhs - other diff = log(mainterm) - log(rhs) soln = _lambert(expand_log(diff), symbol) if not soln: raise NotImplementedError('%s does not appear to have a solution in ' 'terms of LambertW' % f) return list(ordered(soln))
def _matches_commutative(self, expr, repl_dict={}, old=False): """ Matches Add/Mul "pattern" to an expression "expr". repl_dict ... a dictionary of (wild: expression) pairs, that get returned with the results This function is the main workhorse for Add/Mul. Examples ======== >>> from sympy import symbols, Wild, sin >>> a = Wild("a") >>> b = Wild("b") >>> c = Wild("c") >>> x, y, z = symbols("x y z") >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z) {a_: x, b_: y, c_: z} In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is the expression. The repl_dict contains parts that were already matched. For example here: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x}) {a_: x, b_: y, c_: z} the only function of the repl_dict is to return it in the result, e.g. if you omit it: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z) {b_: y, c_: z} the "a: x" is not returned in the result, but otherwise it is equivalent. """ # make sure expr is Expr if pattern is Expr from .expr import Add, Expr from sympy import Mul repl_dict = repl_dict.copy() if isinstance(self, Expr) and not isinstance(expr, Expr): return None # handle simple patterns if self == expr: return repl_dict d = self._matches_simple(expr, repl_dict) if d is not None: return d # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2) from .function import WildFunction from .symbol import Wild wild_part, exact_part = sift( self.args, lambda p: p.has(Wild, WildFunction) and not expr.has(p), binary=True) if not exact_part: wild_part = list(ordered(wild_part)) if self.is_Add: # in addition to normal ordered keys, impose # sorting on Muls with leading Number to put # them in order wild_part = sorted(wild_part, key=lambda x: x.args[0] if x.is_Mul and x.args[0].is_Number else 0) else: exact = self._new_rawargs(*exact_part) free = expr.free_symbols if free and (exact.free_symbols - free): # there are symbols in the exact part that are not # in the expr; but if there are no free symbols, let # the matching continue return None newexpr = self._combine_inverse(expr, exact) if not old and (expr.is_Add or expr.is_Mul): if newexpr.count_ops() > expr.count_ops(): return None newpattern = self._new_rawargs(*wild_part) return newpattern.matches(newexpr, repl_dict) # now to real work ;) i = 0 saw = set() while expr not in saw: saw.add(expr) args = tuple(ordered(self.make_args(expr))) if self.is_Add and expr.is_Add: # in addition to normal ordered keys, impose # sorting on Muls with leading Number to put # them in order args = tuple( sorted(args, key=lambda x: x.args[0] if x.is_Mul and x.args[0].is_Number else 0)) expr_list = (self.identity, ) + args for last_op in reversed(expr_list): for w in reversed(wild_part): d1 = w.matches(last_op, repl_dict) if d1 is not None: d2 = self.xreplace(d1).matches(expr, d1) if d2 is not None: return d2 if i == 0: if self.is_Mul: # make e**i look like Mul if expr.is_Pow and expr.exp.is_Integer: if expr.exp > 0: expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False) else: expr = Mul( *[1 / expr.base, expr.base**(expr.exp + 1)], evaluate=False) i += 1 continue elif self.is_Add: # make i*e look like Add c, e = expr.as_coeff_Mul() if abs(c) > 1: if c > 0: expr = Add(*[e, (c - 1) * e], evaluate=False) else: expr = Add(*[-e, (c + 1) * e], evaluate=False) i += 1 continue # try collection on non-Wild symbols from sympy.simplify.radsimp import collect was = expr did = set() for w in reversed(wild_part): c, w = w.as_coeff_mul(Wild) free = c.free_symbols - did if free: did.update(free) expr = collect(expr, free) if expr != was: i += 0 continue break # if we didn't continue, there is nothing more to do return
def _lambert(eq, x, domain=S.Complexes): """ Given an expression assumed to be in the form ``F(X, a..f) = a*log(b*X + c) + d*X + f = 0`` where X = g(x) and x = g^-1(X), return the Lambert solution, ``x = g^-1(-c/b + (a/d)*W(d/(a*b)*exp(c*d/a/b)*exp(-f/a)))``. """ eq = _mexpand(expand_log(eq)) mainlog = _mostfunc(eq, log, x) if not mainlog: return [] # violated assumptions other = eq.subs(mainlog, 0) if isinstance(-other, log): eq = (eq - other).subs(mainlog, mainlog.args[0]) mainlog = mainlog.args[0] if not isinstance(mainlog, log): return [] # violated assumptions other = -(-other).args[0] eq += other if not x in other.free_symbols: return [] # violated assumptions d, f, X2 = _linab(other, x) logterm = collect(eq - other, mainlog) a = logterm.as_coefficient(mainlog) if a is None or x in a.free_symbols: return [] # violated assumptions logarg = mainlog.args[0] b, c, X1 = _linab(logarg, x) if X1 != X2: return [] # violated assumptions # invert the generator X1 so we have x(u) u = Dummy('rhs') xusolns = solve(X1 - u, x, domain=domain) # There are infinitely many branches for LambertW # but only branches for k = -1 and 0 might be real. The k = 0 # branch is real and the k = -1 branch is real if the LambertW argumen # in in range [-1/e, 0]. Since `solve` does not return infinite # solutions we will only include the -1 branch if it tests as real. # Otherwise, inclusion of any LambertW in the solution indicates to # the user that there are imaginary solutions corresponding to # different k values. lambert_real_branches = [-1, 0] sol = [] # solution of the given Lambert equation is like # sol = -c/b + (a/d)*LambertW(arg, k), # where arg = d/(a*b)*exp((c*d-b*f)/a/b) and k in lambert_real_branches. # Instead of considering the single arg, `d/(a*b)*exp((c*d-b*f)/a/b)`, # the individual `p` roots obtained when writing `exp((c*d-b*f)/a/b)` # as `exp(A/p) = exp(A)**(1/p)`, where `p` is an Integer, are used. # calculating args for LambertW num, den = ((c * d - b * f) / a / b).as_numer_denom() p, den = den.as_coeff_Mul() e = exp(num / den) t = Dummy('t') real = None if p == 1: t = e args = [d / (a * b) * t] elif domain.is_real: ind_ls = [d / (a * b) * t for t in roots(t**p - e, t).keys()] args = [] j = -1 for i in ind_ls: j += 1 if not isinstance(i, int): if not i.has(I): args.append(ind_ls[j]) real = True elif isinstance(i, int): args.append(ind_ls[j]) else: args = [d / (a * b) * t for t in roots(t**p - e, t).keys()] if len(args) == 0: return S.EmptySet # calculating solutions from args for arg in args: if not len(list(eq.atoms(Symbol, Dummy))) > 1: if not domain.is_subset(S.Reals) and ( re(arg) < -1 / E or arg.has(I)) and (not LambertW(arg).is_real) and (not real): integer = Symbol('integer', integer=True) rhs = -c / b + (a / d) * LambertW(arg, integer) for xu in xusolns: sol.append(xu.subs(u, rhs)) else: if -1 / E <= re(arg) < 0 and LambertW(arg).is_real: for k in lambert_real_branches: w = LambertW(arg, k) rhs = -c / b + (a / d) * w for xu in xusolns: sol.append(xu.subs(u, rhs)) elif re(arg) >= 0 and LambertW(arg).is_real: w = LambertW(arg) rhs = -c / b + (a / d) * w for xu in xusolns: sol.append(xu.subs(u, rhs)) elif re(arg) < -1 / E: return S.EmptySet elif (arg.has(Symbol)) and not arg.has(Dummy): if (list(arg.atoms(Symbol))[0]).is_negative and (list( arg.atoms(Symbol))[0]).is_real and domain.is_subset( S.Reals): for k in lambert_real_branches: w = LambertW(arg, k) rhs = -c / b + (a / d) * w if rhs.has(I) or w.has(I): continue for xu in xusolns: sol.append(xu.subs(u, rhs)) if (list(arg.atoms(Symbol))[0]).is_positive and (list( arg.atoms(Symbol))[0]).is_real and domain.is_subset( S.Reals): w = LambertW(arg) rhs = -c / b + (a / d) * w if rhs.has(I) or w.has(I): continue for xu in xusolns: sol.append(xu.subs(u, rhs)) if (list(arg.atoms(Symbol))[0]).is_negative and (list( arg.atoms(Symbol))[0]).is_real and not domain.is_subset( S.Reals): integer = Symbol('integer', integer=True) rhs = -c / b + (a / d) * LambertW(arg, integer) for xu in xusolns: sol.append(xu.subs(u, rhs)) elif domain.is_subset(S.Reals): for k in lambert_real_branches: w = LambertW(arg, k) rhs = -c / b + (a / d) * w if rhs.has(I) or w.has(I): continue for xu in xusolns: sol.append(xu.subs(u, rhs)) elif domain.is_subset(S.Complexes): integer = Symbol('integer', integer=True) rhs = -c / b + (a / d) * LambertW(arg, integer) for xu in xusolns: sol.append(xu.subs(u, rhs)) else: if not domain.is_subset( S.Reals) and (not LambertW(arg).is_real) and (not real): integer = Symbol('integer', integer=True) rhs = Lambda(integer, -c / b + (a / d) * LambertW(arg, integer)) for xu in xusolns: sol.append(xu.subs(u, rhs)) else: w = LambertW(arg) rhs = -c / b + (a / d) * w for xu in xusolns: sol.append(xu.subs(u, rhs)) return list(set(sol))
def _lambert(eq, x): """ Given an expression assumed to be in the form ``F(X, a..f) = a*log(b*X + c) + d*X + f = 0`` where X = g(x) and x = g^-1(X), return the Lambert solution, ``x = g^-1(-c/b + (a/d)*W(d/(a*b)*exp(c*d/a/b)*exp(-f/a)))``. """ eq = _mexpand(expand_log(eq)) mainlog = _mostfunc(eq, log, x) if not mainlog: return [] # violated assumptions other = eq.subs(mainlog, 0) if isinstance(-other, log): eq = (eq - other).subs(mainlog, mainlog.args[0]) mainlog = mainlog.args[0] if not isinstance(mainlog, log): return [] # violated assumptions other = -(-other).args[0] eq += other if not x in other.free_symbols: return [] # violated assumptions d, f, X2 = _linab(other, x) logterm = collect(eq - other, mainlog) a = logterm.as_coefficient(mainlog) if a is None or x in a.free_symbols: return [] # violated assumptions logarg = mainlog.args[0] b, c, X1 = _linab(logarg, x) if X1 != X2: return [] # violated assumptions # invert the generator X1 so we have x(u) u = Dummy('rhs') xusolns = solve(X1 - u, x) # There are infinitely many branches for LambertW # but only branches for k = -1 and 0 might be real. The k = 0 # branch is real and the k = -1 branch is real if the LambertW argumen # in in range [-1/e, 0]. Since `solve` does not return infinite # solutions we will only include the -1 branch if it tests as real. # Otherwise, inclusion of any LambertW in the solution indicates to # the user that there are imaginary solutions corresponding to # different k values. lambert_real_branches = [-1, 0] sol = [] # solution of the given Lambert equation is like # sol = -c/b + (a/d)*LambertW(arg, k), # where arg = d/(a*b)*exp((c*d-b*f)/a/b) and k in lambert_real_branches. # Instead of considering the single arg, `d/(a*b)*exp((c*d-b*f)/a/b)`, # the individual `p` roots obtained when writing `exp((c*d-b*f)/a/b)` # as `exp(A/p) = exp(A)**(1/p)`, where `p` is an Integer, are used. # calculating args for LambertW num, den = ((c*d-b*f)/a/b).as_numer_denom() p, den = den.as_coeff_Mul() e = exp(num/den) t = Dummy('t') args = [d/(a*b)*t for t in roots(t**p - e, t).keys()] # calculating solutions from args for arg in args: for k in lambert_real_branches: w = LambertW(arg, k) if k and not w.is_real: continue rhs = -c/b + (a/d)*w for xu in xusolns: sol.append(xu.subs(u, rhs)) return sol
def _solve_lambert(f, symbol, gens): """Return solution to ``f`` if it is a Lambert-type expression else raise NotImplementedError. For ``f(X, a..f) = a*log(b*X + c) + d*X - f = 0`` the solution for ``X`` is ``X = -c/b + (a/d)*W(d/(a*b)*exp(c*d/a/b)*exp(f/a))``. There are a variety of forms for `f(X, a..f)` as enumerated below: 1a1) if B**B = R for R not in [0, 1] (since those cases would already be solved before getting here) then log of both sides gives log(B) + log(log(B)) = log(log(R)) and X = log(B), a = 1, b = 1, c = 0, d = 1, f = log(log(R)) 1a2) if B*(b*log(B) + c)**a = R then log of both sides gives log(B) + a*log(b*log(B) + c) = log(R) and X = log(B), d=1, f=log(R) 1b) if a*log(b*B + c) + d*B = R and X = B, f = R 2a) if (b*B + c)*exp(d*B + g) = R then log of both sides gives log(b*B + c) + d*B + g = log(R) and X = B, a = 1, f = log(R) - g 2b) if g*exp(d*B + h) - b*B = c then the log form is log(g) + d*B + h - log(b*B + c) = 0 and X = B, a = -1, f = -h - log(g) 3) if d*p**(a*B + g) - b*B = c then the log form is log(d) + (a*B + g)*log(p) - log(b*B + c) = 0 and X = B, a = -1, d = a*log(p), f = -log(d) - g*log(p) """ def _solve_even_degree_expr(expr, t, symbol): """Return the unique solutions of equations derived from ``expr`` by replacing ``t`` with ``+/- symbol``. Parameters ========== expr : Expr The expression which includes a dummy variable t to be replaced with +symbol and -symbol. symbol : Symbol The symbol for which a solution is being sought. Returns ======= List of unique solution of the two equations generated by replacing ``t`` with positive and negative ``symbol``. Notes ===== If ``expr = 2*log(t) + x/2` then solutions for ``2*log(x) + x/2 = 0`` and ``2*log(-x) + x/2 = 0`` are returned by this function. Though this may seem counter-intuitive, one must note that the ``expr`` being solved here has been derived from a different expression. For an expression like ``eq = x**2*g(x) = 1``, if we take the log of both sides we obtain ``log(x**2) + log(g(x)) = 0``. If x is positive then this simplifies to ``2*log(x) + log(g(x)) = 0``; the Lambert-solving routines will return solutions for this, but we must also consider the solutions for ``2*log(-x) + log(g(x))`` since those must also be a solution of ``eq`` which has the same value when the ``x`` in ``x**2`` is negated. If `g(x)` does not have even powers of symbol then we don't want to replace the ``x`` there with ``-x``. So the role of the ``t`` in the expression received by this function is to mark where ``+/-x`` should be inserted before obtaining the Lambert solutions. """ nlhs, plhs = [ expr.xreplace({t: sgn*symbol}) for sgn in (-1, 1)] sols = _solve_lambert(nlhs, symbol, gens) if plhs != nlhs: sols.extend(_solve_lambert(plhs, symbol, gens)) # uniq is needed for a case like # 2*log(t) - log(-z**2) + log(z + log(x) + log(z)) # where subtituting t with +/-x gives all the same solution; # uniq, rather than list(set()), is used to maintain canonical # order return list(uniq(sols)) nrhs, lhs = f.as_independent(symbol, as_Add=True) rhs = -nrhs lamcheck = [tmp for tmp in gens if (tmp.func in [exp, log] or (tmp.is_Pow and symbol in tmp.exp.free_symbols))] if not lamcheck: raise NotImplementedError() if lhs.is_Add or lhs.is_Mul: # replacing all even_degrees of symbol with dummy variable t # since these will need special handling; non-Add/Mul do not # need this handling t = Dummy('t', **symbol.assumptions0) lhs = lhs.replace( lambda i: # find symbol**even i.is_Pow and i.base == symbol and i.exp.is_even, lambda i: # replace t**even t**i.exp) if lhs.is_Add and lhs.has(t): t_indep = lhs.subs(t, 0) t_term = lhs - t_indep _rhs = rhs - t_indep if not t_term.is_Add and _rhs and not ( t_term.has(S.ComplexInfinity, S.NaN)): eq = expand_log(log(t_term) - log(_rhs)) return _solve_even_degree_expr(eq, t, symbol) elif lhs.is_Mul and rhs: # this needs to happen whether t is present or not lhs = expand_log(log(lhs), force=True) rhs = log(rhs) if lhs.has(t) and lhs.is_Add: # it expanded from Mul to Add eq = lhs - rhs return _solve_even_degree_expr(eq, t, symbol) # restore symbol in lhs lhs = lhs.xreplace({t: symbol}) lhs = powsimp(factor(lhs, deep=True)) # make sure we have inverted as completely as possible r = Dummy() i, lhs = _invert(lhs - r, symbol) rhs = i.xreplace({r: rhs}) # For the first forms: # # 1a1) B**B = R will arrive here as B*log(B) = log(R) # lhs is Mul so take log of both sides: # log(B) + log(log(B)) = log(log(R)) # 1a2) B*(b*log(B) + c)**a = R will arrive unchanged so # lhs is Mul, so take log of both sides: # log(B) + a*log(b*log(B) + c) = log(R) # 1b) d*log(a*B + b) + c*B = R will arrive unchanged so # lhs is Add, so isolate c*B and expand log of both sides: # log(c) + log(B) = log(R - d*log(a*B + b)) soln = [] if not soln: mainlog = _mostfunc(lhs, log, symbol) if mainlog: if lhs.is_Mul and rhs != 0: soln = _lambert(log(lhs) - log(rhs), symbol) elif lhs.is_Add: other = lhs.subs(mainlog, 0) if other and not other.is_Add and [ tmp for tmp in other.atoms(Pow) if symbol in tmp.free_symbols]: if not rhs: diff = log(other) - log(other - lhs) else: diff = log(lhs - other) - log(rhs - other) soln = _lambert(expand_log(diff), symbol) else: #it's ready to go soln = _lambert(lhs - rhs, symbol) # For the next forms, # # collect on main exp # 2a) (b*B + c)*exp(d*B + g) = R # lhs is mul, so take log of both sides: # log(b*B + c) + d*B = log(R) - g # 2b) g*exp(d*B + h) - b*B = R # lhs is add, so add b*B to both sides, # take the log of both sides and rearrange to give # log(R + b*B) - d*B = log(g) + h if not soln: mainexp = _mostfunc(lhs, exp, symbol) if mainexp: lhs = collect(lhs, mainexp) if lhs.is_Mul and rhs != 0: soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol) elif lhs.is_Add: # move all but mainexp-containing term to rhs other = lhs.subs(mainexp, 0) mainterm = lhs - other rhs = rhs - other if (mainterm.could_extract_minus_sign() and rhs.could_extract_minus_sign()): mainterm *= -1 rhs *= -1 diff = log(mainterm) - log(rhs) soln = _lambert(expand_log(diff), symbol) # For the last form: # # 3) d*p**(a*B + g) - b*B = c # collect on main pow, add b*B to both sides, # take log of both sides and rearrange to give # a*B*log(p) - log(b*B + c) = -log(d) - g*log(p) if not soln: mainpow = _mostfunc(lhs, Pow, symbol) if mainpow and symbol in mainpow.exp.free_symbols: lhs = collect(lhs, mainpow) if lhs.is_Mul and rhs != 0: # b*B = 0 soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol) elif lhs.is_Add: # move all but mainpow-containing term to rhs other = lhs.subs(mainpow, 0) mainterm = lhs - other rhs = rhs - other diff = log(mainterm) - log(rhs) soln = _lambert(expand_log(diff), symbol) if not soln: raise NotImplementedError('%s does not appear to have a solution in ' 'terms of LambertW' % f) return list(ordered(soln))
def classify_pde(eq, func=None, dict=False, **kwargs): """ Returns a tuple of possible pdsolve() classifications for a PDE. The tuple is ordered so that first item is the classification that pdsolve() uses to solve the PDE by default. In general, classifications near the beginning of the list will produce better solutions faster than those near the end, though there are always exceptions. To make pdsolve use a different classification, use pdsolve(PDE, func, hint=<classification>). See also the pdsolve() docstring for different meta-hints you can use. If ``dict`` is true, classify_pde() will return a dictionary of hint:match expression terms. This is intended for internal use by pdsolve(). Note that because dictionaries are ordered arbitrarily, this will most likely not be in the same order as the tuple. You can get help on different hints by doing help(pde.pde_hintname), where hintname is the name of the hint without "_Integral". See sympy.pde.allhints or the sympy.pde docstring for a list of all supported hints that can be returned from classify_pde. Examples ======== >>> from sympy.solvers.pde import classify_pde >>> from sympy import Function, diff, Eq >>> from sympy.abc import x, y >>> f = Function('f') >>> u = f(x, y) >>> ux = u.diff(x) >>> uy = u.diff(y) >>> eq = Eq(1 + (2*(ux/u)) + (3*(uy/u)), 0) >>> classify_pde(eq) ('1st_linear_constant_coeff_homogeneous',) """ prep = kwargs.pop('prep', True) if func and len(func.args) != 2: raise NotImplementedError( "Right now only partial " "differential equations of two variables are supported") if prep or func is None: prep, func_ = _preprocess(eq, func) if func is None: func = func_ if isinstance(eq, Equality): if eq.rhs != 0: return classify_pde(eq.lhs - eq.rhs, func) eq = eq.lhs f = func.func x = func.args[0] y = func.args[1] fx = f(x, y).diff(x) fy = f(x, y).diff(y) # TODO : For now pde.py uses support offered by the ode_order function # to find the order with respect to a multi-variable function. An # improvement could be to classify the order of the PDE on the basis of # individual variables. order = ode_order(eq, f(x, y)) # hint:matchdict or hint:(tuple of matchdicts) # Also will contain "default":<default hint> and "order":order items. matching_hints = {'order': order} if not order: if dict: matching_hints["default"] = None return matching_hints else: return () eq = expand(eq) a = Wild('a', exclude=[f(x, y)]) b = Wild('b', exclude=[f(x, y), fx, fy, x, y]) c = Wild('c', exclude=[f(x, y), fx, fy, x, y]) d = Wild('d', exclude=[f(x, y), fx, fy, x, y]) e = Wild('e', exclude=[f(x, y), fx, fy]) n = Wild('n', exclude=[x, y]) # Try removing the smallest power of f(x,y) # from the highest partial derivatives of f(x,y) reduced_eq = None if eq.is_Add: var = set(combinations_with_replacement((x, y), order)) dummyvar = var.copy() power = None for i in var: coeff = eq.coeff(f(x, y).diff(*i)) if coeff != 1: match = coeff.match(a * f(x, y)**n) if match and match[a]: power = match[n] dummyvar.remove(i) break dummyvar.remove(i) for i in dummyvar: coeff = eq.coeff(f(x, y).diff(*i)) if coeff != 1: match = coeff.match(a * f(x, y)**n) if match and match[a] and match[n] < power: power = match[n] if power: den = f(x, y)**power reduced_eq = Add(*[arg / den for arg in eq.args]) if not reduced_eq: reduced_eq = eq if order == 1: reduced_eq = collect(reduced_eq, f(x, y)) r = reduced_eq.match(b * fx + c * fy + d * f(x, y) + e) if r: if not r[e]: ## Linear first-order homogeneous partial-differential ## equation with constant coefficients r.update({'b': b, 'c': c, 'd': d}) matching_hints["1st_linear_constant_coeff_homogeneous"] = r else: if r[b]**2 + r[c]**2 != 0: ## Linear first-order general partial-differential ## equation with constant coefficients r.update({'b': b, 'c': c, 'd': d, 'e': e}) matching_hints["1st_linear_constant_coeff"] = r matching_hints["1st_linear_constant_coeff_Integral"] = r else: b = Wild('b', exclude=[f(x, y), fx, fy]) c = Wild('c', exclude=[f(x, y), fx, fy]) d = Wild('d', exclude=[f(x, y), fx, fy]) r = reduced_eq.match(b * fx + c * fy + d * f(x, y) + e) if r: r.update({'b': b, 'c': c, 'd': d, 'e': e}) matching_hints["1st_linear_variable_coeff"] = r # Order keys based on allhints. retlist = [] for i in allhints: if i in matching_hints: retlist.append(i) if dict: # Dictionaries are ordered arbitrarily, so make note of which # hint would come first for pdsolve(). Use an ordered dict in Py 3. matching_hints["default"] = None matching_hints["ordered_hints"] = tuple(retlist) for i in allhints: if i in matching_hints: matching_hints["default"] = i break return matching_hints else: return tuple(retlist)
def log_to_real(h, q, x, t): r""" Convert complex logarithms to real functions. Explanation =========== Given real field K and polynomials h in K[t,x] and q in K[t], returns real function f such that: ___ df d \ ` -- = -- ) a log(h(a, x)) dx dx /__, a | q(a) = 0 Examples ======== >>> from sympy.integrals.rationaltools import log_to_real >>> from sympy.abc import x, y >>> from sympy import Poly, S >>> log_to_real(Poly(x + 3*y/2 + S(1)/2, x, domain='QQ[y]'), ... Poly(3*y**2 + 1, y, domain='ZZ'), x, y) 2*sqrt(3)*atan(2*sqrt(3)*x/3 + sqrt(3)/3)/3 >>> log_to_real(Poly(x**2 - 1, x, domain='ZZ'), ... Poly(-2*y + 1, y, domain='ZZ'), x, y) log(x**2 - 1)/2 See Also ======== log_to_atan """ from sympy.simplify.radsimp import collect u, v = symbols('u,v', cls=Dummy) H = h.as_expr().subs({t: u + I*v}).expand() Q = q.as_expr().subs({t: u + I*v}).expand() H_map = collect(H, I, evaluate=False) Q_map = collect(Q, I, evaluate=False) a, b = H_map.get(S.One, S.Zero), H_map.get(I, S.Zero) c, d = Q_map.get(S.One, S.Zero), Q_map.get(I, S.Zero) R = Poly(resultant(c, d, v), u) R_u = roots(R, filter='R') if len(R_u) != R.count_roots(): return None result = S.Zero for r_u in R_u.keys(): C = Poly(c.subs({u: r_u}), v) R_v = roots(C, filter='R') if len(R_v) != C.count_roots(): return None R_v_paired = [] # take one from each pair of conjugate roots for r_v in R_v: if r_v not in R_v_paired and -r_v not in R_v_paired: if r_v.is_negative or r_v.could_extract_minus_sign(): R_v_paired.append(-r_v) elif not r_v.is_zero: R_v_paired.append(r_v) for r_v in R_v_paired: D = d.subs({u: r_u, v: r_v}) if D.evalf(chop=True) != 0: continue A = Poly(a.subs({u: r_u, v: r_v}), x) B = Poly(b.subs({u: r_u, v: r_v}), x) AB = (A**2 + B**2).as_expr() result += r_u*log(AB) + r_v*log_to_atan(A, B) R_q = roots(q, filter='R') if len(R_q) != q.count_roots(): return None for r in R_q.keys(): result += r*log(h.as_expr().subs(t, r)) return result
def test_collect(): assert collect(A*B - B*A, A) == A*B - B*A assert collect(A*B - B*A, B) == A*B - B*A assert collect(A*B - B*A, x) == A*B - B*A
def _integrate(field=None): irreducibles = set() atans = set() pairs = set() for poly in reducibles: for z in poly.free_symbols: if z in V: break # should this be: `irreducibles |= \ else: # set(root_factors(poly, z, filter=field))` continue # and the line below deleted? # | # V irreducibles |= set(root_factors(poly, z, filter=field)) log_part, atan_part = [], [] for poly in list(irreducibles): m = collect(poly, I, evaluate=False) y = m.get(I, S.Zero) if y: x = m.get(S.One, S.Zero) if x.has(I) or y.has(I): continue # nontrivial x + I*y pairs.add((x, y)) irreducibles.remove(poly) while pairs: x, y = pairs.pop() if (x, -y) in pairs: pairs.remove((x, -y)) # Choosing b with no minus sign if y.could_extract_minus_sign(): y = -y irreducibles.add(x * x + y * y) atans.add(atan(x / y)) else: irreducibles.add(x + I * y) B = _symbols('B', len(irreducibles)) C = _symbols('C', len(atans)) # Note: the ordering matters here for poly, b in reversed(list(ordered(zip(irreducibles, B)))): if poly.has(*V): poly_coeffs.append(b) log_part.append(b * log(poly)) for poly, c in reversed(list(ordered(zip(atans, C)))): if poly.has(*V): poly_coeffs.append(c) atan_part.append(c * poly) # TODO: Currently it's better to use symbolic expressions here instead # of rational functions, because it's simpler and FracElement doesn't # give big speed improvement yet. This is because cancellation is slow # due to slow polynomial GCD algorithms. If this gets improved then # revise this code. candidate = poly_part / poly_denom + Add(*log_part) + Add(*atan_part) h = F - _derivation(candidate) / denom raw_numer = h.as_numer_denom()[0] # Rewrite raw_numer as a polynomial in K[coeffs][V] where K is a field # that we have to determine. We can't use simply atoms() because log(3), # sqrt(y) and similar expressions can appear, leading to non-trivial # domains. syms = set(poly_coeffs) | set(V) non_syms = set([]) def find_non_syms(expr): if expr.is_Integer or expr.is_Rational: pass # ignore trivial numbers elif expr in syms: pass # ignore variables elif not expr.has(*syms): non_syms.add(expr) elif expr.is_Add or expr.is_Mul or expr.is_Pow: list(map(find_non_syms, expr.args)) else: # TODO: Non-polynomial expression. This should have been # filtered out at an earlier stage. raise PolynomialError try: find_non_syms(raw_numer) except PolynomialError: return None else: ground, _ = construct_domain(non_syms, field=True) coeff_ring = PolyRing(poly_coeffs, ground) ring = PolyRing(V, coeff_ring) try: numer = ring.from_expr(raw_numer) except ValueError: raise PolynomialError solution = solve_lin_sys(numer.coeffs(), coeff_ring, _raw=False) if solution is None: return None else: return candidate.subs(solution).subs( list(zip(poly_coeffs, [S.Zero] * len(poly_coeffs))))
def _eval_collect(self, *args, **kwargs): from sympy.simplify.radsimp import collect return Equation(collect(self.lhs, *args, **kwargs), collect(self.rhs, *args, **kwargs))
def _solve_lambert(f, symbol, gens): """Return solution to ``f`` if it is a Lambert-type expression else raise NotImplementedError. The equality, ``f(x, a..f) = a*log(b*X + c) + d*X - f = 0`` has the solution, `X = -c/b + (a/d)*W(d/(a*b)*exp(c*d/a/b)*exp(f/a))`. There are a variety of forms for `f(X, a..f)` as enumerated below: 1a1) if B**B = R for R not [0, 1] then log(B) + log(log(B)) = log(log(R)) X = log(B), a = 1, b = 1, c = 0, d = 1, f = log(log(R)) 1a2) if B*(b*log(B) + c)**a = R then log(B) + a*log(b*log(B) + c) = log(R) X = log(B); d=1, f=log(R) 1b) if a*log(b*B + c) + d*B = R then X = B, f = R 2a) if (b*B + c)*exp(d*B + g) = R then log(b*B + c) + d*B + g = log(R) a = 1, f = log(R) - g, X = B 2b) if -b*B + g*exp(d*B + h) = c then log(g) + d*B + h - log(b*B + c) = 0 a = -1, f = -h - log(g), X = B 3) if d*p**(a*B + g) - b*B = c then log(d) + (a*B + g)*log(p) - log(c + b*B) = 0 a = -1, d = a*log(p), f = -log(d) - g*log(p) """ nrhs, lhs = f.as_independent(symbol, as_Add=True) rhs = -nrhs lamcheck = [ tmp for tmp in gens if (tmp.func in [exp, log] or ( tmp.is_Power and symbol in tmp.exp.free_symbols)) ] if not lamcheck: raise NotImplementedError() if lhs.is_Mul: lhs = expand_log(log(lhs)) rhs = log(rhs) lhs = factor(lhs, deep=True) # make sure we have inverted as completely as possible r = Dummy() i, lhs = _invert(lhs - r, symbol) rhs = i.xreplace({r: rhs}) # For the first ones: # 1a1) B**B = R != 0 (when 0, there is only a solution if the base is 0, # but if it is, the exp is 0 and 0**0=1 # comes back as B*log(B) = log(R) # 1a2) B*(a + b*log(B))**p = R or with monomial expanded or with whole # thing expanded comes back unchanged # log(B) + p*log(a + b*log(B)) = log(R) # lhs is Mul: # expand log of both sides to give: # log(B) + log(log(B)) = log(log(R)) # 1b) d*log(a*B + b) + c*B = R # lhs is Add: # isolate c*B and expand log of both sides: # log(c) + log(B) = log(R - d*log(a*B + b)) soln = [] if not soln: mainlog = _mostfunc(lhs, log, symbol) if mainlog: if lhs.is_Mul and rhs != 0: soln = _lambert(log(lhs) - log(rhs), symbol) elif lhs.is_Add: other = lhs.subs(mainlog, 0) if other and not other.is_Add and [ tmp for tmp in other.atoms(Pow) if symbol in tmp.free_symbols ]: if not rhs: diff = log(other) - log(other - lhs) else: diff = log(lhs - other) - log(rhs - other) soln = _lambert(expand_log(diff), symbol) else: #it's ready to go soln = _lambert(lhs - rhs, symbol) # For the next two, # collect on main exp # 2a) (b*B + c)*exp(d*B + g) = R # lhs is mul: # log to give # log(b*B + c) + d*B = log(R) - g # 2b) -b*B + g*exp(d*B + h) = R # lhs is add: # add b*B # log and rearrange # log(R + b*B) - d*B = log(g) + h if not soln: mainexp = _mostfunc(lhs, exp, symbol) if mainexp: lhs = collect(lhs, mainexp) if lhs.is_Mul and rhs != 0: soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol) elif lhs.is_Add: # move all but mainexp-containing term to rhs other = lhs.subs(mainexp, 0) mainterm = lhs - other rhs = rhs - other if (mainterm.could_extract_minus_sign() and rhs.could_extract_minus_sign()): mainterm *= -1 rhs *= -1 diff = log(mainterm) - log(rhs) soln = _lambert(expand_log(diff), symbol) # 3) d*p**(a*B + b) + c*B = R # collect on main pow # log(R - c*B) - a*B*log(p) = log(d) + b*log(p) if not soln: mainpow = _mostfunc(lhs, Pow, symbol) if mainpow and symbol in mainpow.exp.free_symbols: lhs = collect(lhs, mainpow) if lhs.is_Mul and rhs != 0: soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol) elif lhs.is_Add: # move all but mainpow-containing term to rhs other = lhs.subs(mainpow, 0) mainterm = lhs - other rhs = rhs - other diff = log(mainterm) - log(rhs) soln = _lambert(expand_log(diff), symbol) if not soln: raise NotImplementedError('%s does not appear to have a solution in ' 'terms of LambertW' % f) return list(ordered(soln))
def residue(expr, x, x0): """ Finds the residue of ``expr`` at the point x=x0. The residue is defined as the coefficient of ``1/(x-x0)`` in the power series expansion about ``x=x0``. Examples ======== >>> from sympy import Symbol, residue, sin >>> x = Symbol("x") >>> residue(1/x, x, 0) 1 >>> residue(1/x**2, x, 0) 0 >>> residue(2/sin(x), x, 0) 2 This function is essential for the Residue Theorem [1]. References ========== .. [1] https://en.wikipedia.org/wiki/Residue_theorem """ # The current implementation uses series expansion to # calculate it. A more general implementation is explained in # the section 5.6 of the Bronstein's book {M. Bronstein: # Symbolic Integration I, Springer Verlag (2005)}. For purely # rational functions, the algorithm is much easier. See # sections 2.4, 2.5, and 2.7 (this section actually gives an # algorithm for computing any Laurent series coefficient for # a rational function). The theory in section 2.4 will help to # understand why the resultant works in the general algorithm. # For the definition of a resultant, see section 1.4 (and any # previous sections for more review). from sympy.series.order import Order from sympy.simplify.radsimp import collect expr = sympify(expr) if x0 != 0: expr = expr.subs(x, x + x0) for n in (0, 1, 2, 4, 8, 16, 32): s = expr.nseries(x, n=n) if not s.has(Order) or s.getn() >= 0: break s = collect(s.removeO(), x) if s.is_Add: args = s.args else: args = [s] res = S.Zero for arg in args: c, m = arg.as_coeff_mul(x) m = Mul(*m) if not (m in (S.One, x) or (m.is_Pow and m.exp.is_Integer)): raise NotImplementedError('term of unexpected form: %s' % m) if m == 1 / x: res += c return res