def _sqrtdenest_rec(expr): """Helper that denests the square root of three or more surds. It returns the denested expression; if it cannot be denested it throws SqrtdenestStopIteration Algorithm: expr.base is in the extension Q_m = Q(sqrt(r_1),..,sqrt(r_k)); split expr.base = a + b*sqrt(r_k), where `a` and `b` are on Q_(m-1) = Q(sqrt(r_1),..,sqrt(r_(k-1))); then a**2 - b**2*r_k is on Q_(m-1); denest sqrt(a**2 - b**2*r_k) and so on. See [1], section 6. Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrtdenest_rec >>> _sqrtdenest_rec(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498)) -sqrt(10) + sqrt(2) + 9 + 9*sqrt(5) >>> w=-6*sqrt(55)-6*sqrt(35)-2*sqrt(22)-2*sqrt(14)+2*sqrt(77)+6*sqrt(10)+65 >>> _sqrtdenest_rec(sqrt(w)) -sqrt(11) - sqrt(7) + sqrt(2) + 3*sqrt(5) """ from sympy.simplify.simplify import radsimp, split_surds, rad_rationalize if not expr.is_Pow: return sqrtdenest(expr) if expr.base < 0: return sqrt(-1)*_sqrtdenest_rec(sqrt(-expr.base)) g, a, b = split_surds(expr.base) a = a*sqrt(g) if a < b: a, b = b, a c2 = _mexpand(a**2 - b**2) if len(c2.args) > 2: g, a1, b1 = split_surds(c2) a1 = a1*sqrt(g) if a1 < b1: a1, b1 = b1, a1 c2_1 = _mexpand(a1**2 - b1**2) c_1 = _sqrtdenest_rec(sqrt(c2_1)) d_1 = _sqrtdenest_rec(sqrt(a1 + c_1)) num, den = rad_rationalize(b1, d_1) c = _mexpand(d_1/sqrt(2) + num/(den*sqrt(2))) else: c = _sqrtdenest1(sqrt(c2)) if sqrt_depth(c) > 1: raise SqrtdenestStopIteration ac = a + c if len(ac.args) >= len(expr.args): if count_ops(ac) >= count_ops(expr.base): raise SqrtdenestStopIteration d = sqrtdenest(sqrt(ac)) if sqrt_depth(d) > 1: raise SqrtdenestStopIteration num, den = rad_rationalize(b, d) r = d/sqrt(2) + num/(den*sqrt(2)) r = radsimp(r) return _mexpand(r)
def _sqrt_symbolic_denest(a, b, r): """Given an expression, sqrt(a + b*sqrt(b)), return the denested expression or None. Algorithm: If r = ra + rb*sqrt(rr), try replacing sqrt(rr) in ``a`` with (y**2 - ra)/rb, and if the result is a quadratic, ca*y**2 + cb*y + cc, and (cb + b)**2 - 4*ca*cc is 0, then sqrt(a + b*sqrt(r)) can be rewritten as sqrt(ca*(sqrt(r) + (cb + b)/(2*ca))**2). Examples ======== >>> from sympy.simplify.sqrtdenest import _sqrt_symbolic_denest, sqrtdenest >>> from sympy import sqrt, Symbol >>> from sympy.abc import x >>> a, b, r = 16 - 2*sqrt(29), 2, -10*sqrt(29) + 55 >>> _sqrt_symbolic_denest(a, b, r) sqrt(-2*sqrt(29) + 11) + sqrt(5) If the expression is numeric, it will be simplified: >>> w = sqrt(sqrt(sqrt(3) + 1) + 1) + 1 + sqrt(2) >>> sqrtdenest(sqrt((w**2).expand())) 1 + sqrt(2) + sqrt(1 + sqrt(1 + sqrt(3))) Otherwise, it will only be simplified if assumptions allow: >>> w = w.subs(sqrt(3), sqrt(x + 3)) >>> sqrtdenest(sqrt((w**2).expand())) sqrt((sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2))**2) Notice that the argument of the sqrt is a square. If x is made positive then the sqrt of the square is resolved: >>> _.subs(x, Symbol('x', positive=True)) sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2) """ a, b, r = map(sympify, (a, b, r)) rval = _sqrt_match(r) if not rval: return None ra, rb, rr = rval if rb: y = Dummy('y', positive=True) try: newa = Poly(a.subs(sqrt(rr), (y**2 - ra)/rb), y) except PolynomialError: return None if newa.degree() == 2: ca, cb, cc = newa.all_coeffs() cb += b if _mexpand(cb**2 - 4*ca*cc).equals(0): z = sqrt(ca*(sqrt(r) + cb/(2*ca))**2) if z.is_number: z = _mexpand(Mul._from_args(z.as_content_primitive())) return z
def _sqrtdenest1(expr, denester=True): """Return denested expr after denesting with simpler methods or, that failing, using the denester.""" from sympy.simplify.simplify import radsimp if not is_sqrt(expr): return expr a = expr.base if a.is_Atom: return expr val = _sqrt_match(a) if not val: return expr a, b, r = val # try a quick numeric denesting d2 = _mexpand(a**2 - b**2*r) if d2.is_Rational: if d2.is_positive: z = _sqrt_numeric_denest(a, b, r, d2) if z is not None: return z else: # fourth root case # sqrtdenest(sqrt(3 + 2*sqrt(3))) = # sqrt(2)*3**(1/4)/2 + sqrt(2)*3**(3/4)/2 dr2 = _mexpand(-d2*r) dr = sqrt(dr2) if dr.is_Rational: z = _sqrt_numeric_denest(_mexpand(b*r), a, r, dr2) if z is not None: return z/root(r, 4) else: z = _sqrt_symbolic_denest(a, b, r) if z is not None: return z if not denester or not is_algebraic(expr): return expr res = sqrt_biquadratic_denest(expr, a, b, r, d2) if res: return res # now call to the denester av0 = [a, b, r, d2] z = _denester([radsimp(expr**2)], av0, 0, sqrt_depth(expr))[0] if av0[1] is None: return expr if z is not None: if sqrt_depth(z) == sqrt_depth(expr) and count_ops(z) > count_ops(expr): return expr return z return expr
def is_pell_transformation_ok(eq): """ Test whether X*Y, X, or Y terms are present in the equation after transforming the equation using the transformation returned by transformation_to_pell(). If they are not present we are good. Moreover, coefficient of X**2 should be a divisor of coefficient of Y**2 and the constant term. """ A, B = transformation_to_DN(eq) u = (A*Matrix([X, Y]) + B)[0] v = (A*Matrix([X, Y]) + B)[1] simplified = _mexpand(Subs(eq, (x, y), (u, v)).doit()) coeff = dict([reversed(t.as_independent(*[X, Y])) for t in simplified.args]) for term in [X*Y, X, Y]: if term in coeff.keys(): return False for term in [X**2, Y**2, Integer(1)]: if term not in coeff.keys(): coeff[term] = Integer(0) if coeff[X**2] != 0: return isinstance(S(coeff[Y**2])/coeff[X**2], Integer) and isinstance(S(coeff[Integer(1)])/coeff[X**2], Integer) return True
def check_solutions(eq): """ Determines whether solutions returned by diophantine() satisfy the original equation. Hope to generalize this so we can remove functions like check_ternay_quadratic, check_solutions_normal, check_solutions() """ s = diophantine(eq) terms = factor_list(eq)[1] var = list(eq.free_symbols) var.sort(key=default_sort_key) okay = True while len(s) and okay: solution = s.pop() okay = False for term in terms: subeq = term[0] if simplify(_mexpand(Subs(subeq, var, solution).doit())) == 0: okay = True break return okay
def _minpoly_cos(ex, x): """ Returns the minimal polynomial of ``cos(ex)`` see http://mathworld.wolfram.com/TrigonometryAngles.html """ from sympy import sqrt c, a = ex.args[0].as_coeff_Mul() if a is pi: if c.is_rational: if c.p == 1: if c.q == 7: return 8*x**3 - 4*x**2 - 4*x + 1 if c.q == 9: return 8*x**3 - 6*x + 1 elif c.p == 2: q = sympify(c.q) if q.is_prime: s = _minpoly_sin(ex, x) return _mexpand(s.subs({x:sqrt((1 - x)/2)})) # for a = pi*p/q, cos(q*a) =T_q(cos(a)) = (-1)**p n = int(c.q) a = dup_chebyshevt(n, ZZ) a = [x**(n - i)*a[i] for i in range(n + 1)] r = Add(*a) - (-1)**c.p _, factors = factor_list(r) res = _choose_factor(factors, x, ex) return res raise NotAlgebraic("%s doesn't seem to be an algebraic element" % ex)
def _sqrtdenest0(expr): """Returns expr after denesting its arguments.""" if is_sqrt(expr): n, d = expr.as_numer_denom() if d is S.One: # n is a square root if n.base.is_Add: args = sorted(n.base.args, key=default_sort_key) if len(args) > 2 and all((x**2).is_Integer for x in args): try: return _sqrtdenest_rec(n) except SqrtdenestStopIteration: pass expr = sqrt(_mexpand(Add(*[_sqrtdenest0(x) for x in args]))) return _sqrtdenest1(expr) else: n, d = [_sqrtdenest0(i) for i in (n, d)] return n/d if isinstance(expr, Add): cs = [] args = [] for arg in expr.args: c, a = arg.as_coeff_Mul() cs.append(c) args.append(a) if all(c.is_Rational for c in cs) and all(is_sqrt(arg) for arg in args): return _sqrt_ratcomb(cs, args) if isinstance(expr, Expr): args = expr.args if args: return expr.func(*[_sqrtdenest0(a) for a in args]) return expr
def find(a): n = len(a) for i in range(n - 1): for j in range(i + 1, n): s1 = a[i].base s2 = a[j].base p = _mexpand(s1 * s2) s = sqrtdenest(sqrt(p)) if s != sqrt(p): return s, i, j
def nthroot(expr, n, max_len=4, prec=15): """ compute a real nth-root of a sum of surds Parameters ========== expr : sum of surds n : integer max_len : maximum number of surds passed as constants to ``nsimplify`` Algorithm ========= First ``nsimplify`` is used to get a candidate root; if it is not a root the minimal polynomial is computed; the answer is one of its roots. Examples ======== >>> from sympy.simplify.simplify import nthroot >>> from sympy import Rational, sqrt >>> nthroot(90 + 34*sqrt(7), 3) sqrt(7) + 3 """ expr = sympify(expr) n = sympify(n) p = expr**Rational(1, n) if not n.is_integer: return p if not _is_sum_surds(expr): return p surds = [] coeff_muls = [x.as_coeff_Mul() for x in expr.args] for x, y in coeff_muls: if not x.is_rational: return p if y is S.One: continue if not (y.is_Pow and y.exp == S.Half and y.base.is_integer): return p surds.append(y) surds.sort() surds = surds[:max_len] if expr < 0 and n % 2 == 1: p = (-expr)**Rational(1, n) a = nsimplify(p, constants=surds) res = a if _mexpand(a**n) == _mexpand(-expr) else p return -res a = nsimplify(p, constants=surds) if _mexpand(a) is not _mexpand(p) and _mexpand(a**n) == _mexpand(expr): return _mexpand(a) expr = _nthroot_solve(expr, n, prec) if expr is None: return p return expr
def is_normal_transformation_ok(eq): A = transformation_to_normal(eq) X, Y, Z = A*Matrix([x, y, z]) simplified = _mexpand(Subs(eq, (x, y, z), (X, Y, Z)).doit()) coeff = dict([reversed(t.as_independent(*[X, Y, Z])) for t in simplified.args]) for term in [X*Y, Y*Z, X*Z]: if term in coeff.keys(): return False return True
def rad_rationalize(num, den): """ Rationalize num/den by removing square roots in the denominator; num and den are sum of terms whose squares are rationals Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.radsimp import rad_rationalize >>> rad_rationalize(sqrt(3), 1 + sqrt(2)/3) (-sqrt(3) + sqrt(6)/3, -7/9) """ if not den.is_Add: return num, den g, a, b = split_surds(den) a = a*sqrt(g) num = _mexpand((a - b)*num) den = _mexpand(a**2 - b**2) return rad_rationalize(num, den)
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 _nthroot_solve(p, n, prec): """ helper function for ``nthroot`` It denests ``p**Rational(1, n)`` using its minimal polynomial """ from sympy.polys.numberfields import _minimal_polynomial_sq from sympy.solvers import solve while n % 2 == 0: p = sqrtdenest(sqrt(p)) n = n // 2 if n == 1: return p pn = p**Rational(1, n) x = Symbol('x') f = _minimal_polynomial_sq(p, n, x) if f is None: return None sols = solve(f, x) for sol in sols: if abs(sol - pn).n() < 1./10**prec: sol = sqrtdenest(sol) if _mexpand(sol**n) == p: return sol
def _sqrtdenest0(expr): """Returns expr after denesting its arguments.""" if is_sqrt(expr): n, d = expr.as_numer_denom() if d is S.One: # n is a square root if n.base.is_Add: args = sorted(n.base.args, key=default_sort_key) if len(args) > 2 and all((x**2).is_Integer for x in args): try: return _sqrtdenest_rec(n) except SqrtdenestStopIteration: pass expr = sqrt(_mexpand(Add(*[_sqrtdenest0(x) for x in args]))) return _sqrtdenest1(expr) else: n, d = [_sqrtdenest0(i) for i in (n, d)] return n/d if isinstance(expr, Expr): args = expr.args if args: return expr.func(*[_sqrtdenest0(a) for a in args]) return expr
def sqrt_biquadratic_denest(expr, a, b, r, d2): """denest expr = sqrt(a + b*sqrt(r)) where a, b, r are linear combinations of square roots of positive rationals on the rationals (SQRR) and r > 0, b != 0, d2 = a**2 - b**2*r > 0 If it cannot denest it returns None. ALGORITHM Search for a solution A of type SQRR of the biquadratic equation 4*A**4 - 4*a*A**2 + b**2*r = 0 (1) sqd = sqrt(a**2 - b**2*r) Choosing the sqrt to be positive, the possible solutions are A = sqrt(a/2 +/- sqd/2) Since a, b, r are SQRR, then a**2 - b**2*r is a SQRR, so if sqd can be denested, it is done by _sqrtdenest_rec, and the result is a SQRR. Similarly for A. Examples of solutions (in both cases a and sqd are positive): Example of expr with solution sqrt(a/2 + sqd/2) but not solution sqrt(a/2 - sqd/2): expr = sqrt(-sqrt(15) - sqrt(2)*sqrt(-sqrt(5) + 5) - sqrt(3) + 8) a = -sqrt(15) - sqrt(3) + 8; sqd = -2*sqrt(5) - 2 + 4*sqrt(3) Example of expr with solution sqrt(a/2 - sqd/2) but not solution sqrt(a/2 + sqd/2): w = 2 + r2 + r3 + (1 + r3)*sqrt(2 + r2 + 5*r3) expr = sqrt((w**2).expand()) a = 4*sqrt(6) + 8*sqrt(2) + 47 + 28*sqrt(3) sqd = 29 + 20*sqrt(3) Define B = b/2*A; eq.(1) implies a = A**2 + B**2*r; then expr**2 = a + b*sqrt(r) = (A + B*sqrt(r))**2 Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match, sqrt_biquadratic_denest >>> z = sqrt((2*sqrt(2) + 4)*sqrt(2 + sqrt(2)) + 5*sqrt(2) + 8) >>> a, b, r = _sqrt_match(z**2) >>> d2 = a**2 - b**2*r >>> sqrt_biquadratic_denest(z, a, b, r, d2) sqrt(2) + sqrt(sqrt(2) + 2) + 2 """ from sympy.simplify.simplify import radsimp, rad_rationalize if r <= 0 or d2 < 0 or not b or sqrt_depth(expr.base) < 2: return None for x in (a, b, r): for y in x.args: y2 = y**2 if not y2.is_Integer or not y2.is_positive: return None sqd = _mexpand(sqrtdenest(sqrt(radsimp(d2)))) if sqrt_depth(sqd) > 1: return None x1, x2 = [a/2 + sqd/2, a/2 - sqd/2] # look for a solution A with depth 1 for x in (x1, x2): A = sqrtdenest(sqrt(x)) if sqrt_depth(A) > 1: continue Bn, Bd = rad_rationalize(b, _mexpand(2*A)) B = Bn/Bd z = A + B*sqrt(r) if z < 0: z = -z return _mexpand(z) return None
def handle(expr): # Handle first reduces to the case # expr = 1/d, where d is an add, or d is base**p/2. # We do this by recursively calling handle on each piece. from sympy.simplify.simplify import nsimplify n, d = fraction(expr) if expr.is_Atom or (d.is_Atom and n.is_Atom): return expr elif not n.is_Atom: n = n.func(*[handle(a) for a in n.args]) return _unevaluated_Mul(n, handle(1 / d)) elif n is not S.One: return _unevaluated_Mul(n, handle(1 / d)) elif d.is_Mul: return _unevaluated_Mul(*[handle(1 / d) for d in d.args]) # By this step, expr is 1/d, and d is not a mul. if not symbolic and d.free_symbols: return expr if ispow2(d): d2 = sqrtdenest(sqrt(d.base))**numer(d.exp) if d2 != d: return handle(1 / d2) elif d.is_Pow and (d.exp.is_integer or d.base.is_positive): # (1/d**i) = (1/d)**i return handle(1 / d.base)**d.exp if not (d.is_Add or ispow2(d)): return 1 / d.func(*[handle(a) for a in d.args]) # handle 1/d treating d as an Add (though it may not be) keep = True # keep changes that are made # flatten it and collect radicals after checking for special # conditions d = _mexpand(d) # did it change? if d.is_Atom: return 1 / d # is it a number that might be handled easily? if d.is_number: _d = nsimplify(d) if _d.is_Number and _d.equals(d): return 1 / _d while True: # collect similar terms collected = defaultdict(list) for m in Add.make_args(d): # d might have become non-Add p2 = [] other = [] for i in Mul.make_args(m): if ispow2(i, log2=True): p2.append(i.base if i.exp is S.Half else i.base**( 2 * i.exp)) elif i is S.ImaginaryUnit: p2.append(S.NegativeOne) else: other.append(i) collected[tuple(ordered(p2))].append(Mul(*other)) rterms = list(ordered(list(collected.items()))) rterms = [(Mul(*i), Add(*j)) for i, j in rterms] nrad = len(rterms) - (1 if rterms[0][0] is S.One else 0) if nrad < 1: break elif nrad > max_terms: # there may have been invalid operations leading to this point # so don't keep changes, e.g. this expression is troublesome # in collecting terms so as not to raise the issue of 2834: # r = sqrt(sqrt(5) + 5) # eq = 1/(sqrt(5)*r + 2*sqrt(5)*sqrt(-sqrt(5) + 5) + 5*r) keep = False break if len(rterms) > 4: # in general, only 4 terms can be removed with repeated squaring # but other considerations can guide selection of radical terms # so that radicals are removed if all( [x.is_Integer and (y**2).is_Rational for x, y in rterms]): nd, d = rad_rationalize( S.One, Add._from_args([sqrt(x) * y for x, y in rterms])) n *= nd else: # is there anything else that might be attempted? keep = False break from sympy.simplify.powsimp import powsimp, powdenest num = powsimp(_num(rterms)) n *= num d *= num d = powdenest(_mexpand(d), force=symbolic) if d.is_Atom: break if not keep: return expr return _unevaluated_Mul(n, 1 / d)
def minimal_polynomial(ex, x=None, compose=True, polys=False, domain=None): """ Computes the minimal polynomial of an algebraic element. Parameters ========== ex : Expr Element or expression whose minimal polynomial is to be calculated. x : Symbol, optional Independent variable of the minimal polynomial compose : boolean, optional (default=True) Method to use for computing minimal polynomial. If ``compose=True`` (default) then ``_minpoly_compose`` is used, if ``compose=False`` then groebner bases are used. polys : boolean, optional (default=False) If ``True`` returns a ``Poly`` object else an ``Expr`` object. domain : Domain, optional Ground domain Notes ===== By default ``compose=True``, the minimal polynomial of the subexpressions of ``ex`` are computed, then the arithmetic operations on them are performed using the resultant and factorization. If ``compose=False``, a bottom-up algorithm is used with ``groebner``. The default algorithm stalls less frequently. If no ground domain is given, it will be generated automatically from the expression. Examples ======== >>> from sympy import minimal_polynomial, sqrt, solve, QQ >>> from sympy.abc import x, y >>> minimal_polynomial(sqrt(2), x) x**2 - 2 >>> minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) x - sqrt(2) >>> minimal_polynomial(sqrt(2) + sqrt(3), x) x**4 - 10*x**2 + 1 >>> minimal_polynomial(solve(x**3 + x + 3)[0], x) x**3 + x + 3 >>> minimal_polynomial(sqrt(y), x) x**2 - y """ ex = sympify(ex) if ex.is_number: # not sure if it's always needed but try it for numbers (issue 8354) ex = _mexpand(ex, recursive=True) for expr in preorder_traversal(ex): if expr.is_AlgebraicNumber: compose = False break if x is not None: x, cls = sympify(x), Poly else: x, cls = Dummy('x'), PurePoly if not domain: if ex.free_symbols: domain = FractionField(QQ, list(ex.free_symbols)) else: domain = QQ if hasattr(domain, 'symbols') and x in domain.symbols: raise GeneratorsError("the variable %s is an element of the ground " "domain %s" % (x, domain)) if compose: result = _minpoly_compose(ex, x, domain) result = result.primitive()[1] c = result.coeff(x**degree(result, x)) if c.is_negative: result = expand_mul(-result) return cls(result, x, field=True) if polys else result.collect(x) if not domain.is_QQ: raise NotImplementedError("groebner method only works for QQ") result = _minpoly_groebner(ex, x, cls) return cls(result, x, field=True) if polys else result.collect(x)
def simplify(expr, ratio=1.7, measure=count_ops, fu=False): """ Simplifies the given expression. Simplification is not a well defined term and the exact strategies this function tries can change in the future versions of SymPy. If your algorithm relies on "simplification" (whatever it is), try to determine what you need exactly - is it powsimp()?, radsimp()?, together()?, logcombine()?, or something else? And use this particular function directly, because those are well defined and thus your algorithm will be robust. Nonetheless, especially for interactive use, or when you don't know anything about the structure of the expression, simplify() tries to apply intelligent heuristics to make the input expression "simpler". For example: >>> from sympy import simplify, cos, sin >>> from sympy.abc import x, y >>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2) >>> a (x**2 + x)/(x*sin(y)**2 + x*cos(y)**2) >>> simplify(a) x + 1 Note that we could have obtained the same result by using specific simplification functions: >>> from sympy import trigsimp, cancel >>> trigsimp(a) (x**2 + x)/x >>> cancel(_) x + 1 In some cases, applying :func:`simplify` may actually result in some more complicated expression. The default ``ratio=1.7`` prevents more extreme cases: if (result length)/(input length) > ratio, then input is returned unmodified. The ``measure`` parameter lets you specify the function used to determine how complex an expression is. The function should take a single argument as an expression and return a number such that if expression ``a`` is more complex than expression ``b``, then ``measure(a) > measure(b)``. The default measure function is :func:`count_ops`, which returns the total number of operations in the expression. For example, if ``ratio=1``, ``simplify`` output can't be longer than input. :: >>> from sympy import sqrt, simplify, count_ops, oo >>> root = 1/(sqrt(2)+3) Since ``simplify(root)`` would result in a slightly longer expression, root is returned unchanged instead:: >>> simplify(root, ratio=1) == root True If ``ratio=oo``, simplify will be applied anyway:: >>> count_ops(simplify(root, ratio=oo)) > count_ops(root) True Note that the shortest expression is not necessary the simplest, so setting ``ratio`` to 1 may not be a good idea. Heuristically, the default value ``ratio=1.7`` seems like a reasonable choice. You can easily define your own measure function based on what you feel should represent the "size" or "complexity" of the input expression. Note that some choices, such as ``lambda expr: len(str(expr))`` may appear to be good metrics, but have other problems (in this case, the measure function may slow down simplify too much for very large expressions). If you don't know what a good metric would be, the default, ``count_ops``, is a good one. For example: >>> from sympy import symbols, log >>> a, b = symbols('a b', positive=True) >>> g = log(a) + log(b) + log(a)*log(1/b) >>> h = simplify(g) >>> h log(a*b**(-log(a) + 1)) >>> count_ops(g) 8 >>> count_ops(h) 5 So you can see that ``h`` is simpler than ``g`` using the count_ops metric. However, we may not like how ``simplify`` (in this case, using ``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way to reduce this would be to give more weight to powers as operations in ``count_ops``. We can do this by using the ``visual=True`` option: >>> print(count_ops(g, visual=True)) 2*ADD + DIV + 4*LOG + MUL >>> print(count_ops(h, visual=True)) 2*LOG + MUL + POW + SUB >>> from sympy import Symbol, S >>> def my_measure(expr): ... POW = Symbol('POW') ... # Discourage powers by giving POW a weight of 10 ... count = count_ops(expr, visual=True).subs(POW, 10) ... # Every other operation gets a weight of 1 (the default) ... count = count.replace(Symbol, type(S.One)) ... return count >>> my_measure(g) 8 >>> my_measure(h) 14 >>> 15./8 > 1.7 # 1.7 is the default ratio True >>> simplify(g, measure=my_measure) -log(a)*log(b) + log(a) + log(b) Note that because ``simplify()`` internally tries many different simplification strategies and then compares them using the measure function, we get a completely different result that is still different from the input expression by doing this. """ expr = sympify(expr) try: return expr._eval_simplify(ratio=ratio, measure=measure) except AttributeError: pass original_expr = expr = signsimp(expr) from sympy.simplify.hyperexpand import hyperexpand from sympy.functions.special.bessel import BesselBase from sympy import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, ExpBase)): if isinstance(expr, Function) and hasattr(expr, "inverse"): if len(expr.args) == 1 and len(expr.args[0].args) == 1 and \ isinstance(expr.args[0], expr.inverse(argindex=1)): return simplify(expr.args[0].args[0], ratio=ratio, measure=measure, fu=fu) return expr.func(*[ simplify(x, ratio=ratio, measure=measure, fu=fu) for x in expr.args ]) # TODO: Apply different strategies, considering expression pattern: # is it a purely rational function? Is there any trigonometric function?... # See also https://github.com/sympy/sympy/pull/185. def shorter(*choices): '''Return the choice that has the fewest ops. In case of a tie, the expression listed first is selected.''' if not has_variety(choices): return choices[0] return min(choices, key=measure) expr = bottom_up(expr, lambda w: w.normal()) expr = Mul(*powsimp(expr).as_content_primitive()) _e = cancel(expr) expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829 expr2 = shorter(together(expr, deep=True), together(expr1, deep=True)) if ratio is S.Infinity: expr = expr2 else: expr = shorter(expr2, expr1, expr) if not isinstance(expr, Basic): # XXX: temporary hack return expr expr = factor_terms(expr, sign=False) # hyperexpand automatically only works on hypergeometric terms expr = hyperexpand(expr) expr = piecewise_fold(expr) if expr.has(BesselBase): expr = besselsimp(expr) if expr.has(TrigonometricFunction) and not fu or expr.has( HyperbolicFunction): expr = trigsimp(expr, deep=True) if expr.has(log): expr = shorter(expand_log(expr, deep=True), logcombine(expr)) if expr.has(CombinatorialFunction, gamma): expr = combsimp(expr) if expr.has(Sum): expr = sum_simplify(expr) if expr.has(Product): expr = product_simplify(expr) short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr) short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short))) if short.has(TrigonometricFunction, HyperbolicFunction, ExpBase): short = exptrigsimp(short, simplify=False) # get rid of hollow 2-arg Mul factorization hollow_mul = Transform( lambda x: Mul(*x.args), lambda x: x.is_Mul and len(x.args) == 2 and x. args[0].is_Number and x.args[1].is_Add and x.is_commutative) expr = short.xreplace(hollow_mul) numer, denom = expr.as_numer_denom() if denom.is_Add: n, d = fraction(radsimp(1 / denom, symbolic=False, max_terms=1)) if n is not S.One: expr = (numer * n).expand() / d if expr.could_extract_minus_sign(): n, d = fraction(expr) if d != 0: expr = signsimp(-n / (-d)) if measure(expr) > ratio * measure(original_expr): expr = original_expr return expr
def _solve_undetermined_coefficients(eq, func, order, match, trialset): r""" Helper function for the method of undetermined coefficients. See the :py:meth:`~sympy.solvers.ode.single.NthLinearConstantCoeffUndeterminedCoefficients` docstring for more information on this method. The parameter ``trialset`` is the set of trial functions as returned by ``_undetermined_coefficients_match()['trialset']``. The parameter ``match`` should be a dictionary that has the following keys: ``list`` A list of solutions to the homogeneous equation. ``sol`` The general solution. """ r = match coeffs = numbered_symbols('a', cls=Dummy) coefflist = [] gensols = r['list'] gsol = r['sol'] f = func.func x = func.args[0] if len(gensols) != order: raise NotImplementedError( "Cannot find " + str(order) + " solutions to the homogeneous equation necessary to apply" + " undetermined coefficients to " + str(eq) + " (number of terms != order)") trialfunc = 0 for i in trialset: c = next(coeffs) coefflist.append(c) trialfunc += c * i eqs = sub_func_doit(eq, f(x), trialfunc) coeffsdict = dict(list(zip(trialset, [0] * (len(trialset) + 1)))) eqs = _mexpand(eqs) for i in Add.make_args(eqs): s = separatevars(i, dict=True, symbols=[x]) if coeffsdict.get(s[x]): coeffsdict[s[x]] += s['coeff'] else: coeffsdict[s[x]] = s['coeff'] coeffvals = solve(list(coeffsdict.values()), coefflist) if not coeffvals: raise NotImplementedError("Could not solve `%s` using the " "method of undetermined coefficients " "(unable to solve for coefficients)." % eq) psol = trialfunc.subs(coeffvals) return Eq(f(x), gsol.rhs + psol)
def factor_nc_test(e): ex = _mexpand(e) assert ex.is_Add f = factor_nc(ex) assert not f.is_Add and _mexpand(f) == ex
def _sqrtdenest_rec(expr): """Helper that denests the square root of three or more surds. Explanation =========== It returns the denested expression; if it cannot be denested it throws SqrtdenestStopIteration Algorithm: expr.base is in the extension Q_m = Q(sqrt(r_1),..,sqrt(r_k)); split expr.base = a + b*sqrt(r_k), where `a` and `b` are on Q_(m-1) = Q(sqrt(r_1),..,sqrt(r_(k-1))); then a**2 - b**2*r_k is on Q_(m-1); denest sqrt(a**2 - b**2*r_k) and so on. See [1], section 6. Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrtdenest_rec >>> _sqrtdenest_rec(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498)) -sqrt(10) + sqrt(2) + 9 + 9*sqrt(5) >>> w=-6*sqrt(55)-6*sqrt(35)-2*sqrt(22)-2*sqrt(14)+2*sqrt(77)+6*sqrt(10)+65 >>> _sqrtdenest_rec(sqrt(w)) -sqrt(11) - sqrt(7) + sqrt(2) + 3*sqrt(5) """ from sympy.simplify.radsimp import radsimp, rad_rationalize, split_surds if not expr.is_Pow: return sqrtdenest(expr) if expr.base < 0: return sqrt(-1)*_sqrtdenest_rec(sqrt(-expr.base)) g, a, b = split_surds(expr.base) a = a*sqrt(g) if a < b: a, b = b, a c2 = _mexpand(a**2 - b**2) if len(c2.args) > 2: g, a1, b1 = split_surds(c2) a1 = a1*sqrt(g) if a1 < b1: a1, b1 = b1, a1 c2_1 = _mexpand(a1**2 - b1**2) c_1 = _sqrtdenest_rec(sqrt(c2_1)) d_1 = _sqrtdenest_rec(sqrt(a1 + c_1)) num, den = rad_rationalize(b1, d_1) c = _mexpand(d_1/sqrt(2) + num/(den*sqrt(2))) else: c = _sqrtdenest1(sqrt(c2)) if sqrt_depth(c) > 1: raise SqrtdenestStopIteration ac = a + c if len(ac.args) >= len(expr.args): if count_ops(ac) >= count_ops(expr.base): raise SqrtdenestStopIteration d = sqrtdenest(sqrt(ac)) if sqrt_depth(d) > 1: raise SqrtdenestStopIteration num, den = rad_rationalize(b, d) r = d/sqrt(2) + num/(den*sqrt(2)) r = radsimp(r) return _mexpand(r)
def _separate_sq(p): """ helper function for ``_minimal_polynomial_sq`` It selects a rational ``g`` such that the polynomial ``p`` consists of a sum of terms whose surds squared have gcd equal to ``g`` and a sum of terms with surds squared prime with ``g``; then it takes the field norm to eliminate ``sqrt(g)`` See simplify.simplify.split_surds and polytools.sqf_norm. Examples ======== >>> from sympy import sqrt >>> from sympy.abc import x >>> from sympy.polys.numberfields import _separate_sq >>> p= -x + sqrt(2) + sqrt(3) + sqrt(7) >>> p = _separate_sq(p); p -x**2 + 2*sqrt(3)*x + 2*sqrt(7)*x - 2*sqrt(21) - 8 >>> p = _separate_sq(p); p -x**4 + 4*sqrt(7)*x**3 - 32*x**2 + 8*sqrt(7)*x + 20 >>> p = _separate_sq(p); p -x**8 + 48*x**6 - 536*x**4 + 1728*x**2 - 400 """ from sympy.utilities.iterables import sift def is_sqrt(expr): return expr.is_Pow and expr.exp is S.Half # p = c1*sqrt(q1) + ... + cn*sqrt(qn) -> a = [(c1, q1), .., (cn, qn)] a = [] for y in p.args: if not y.is_Mul: if is_sqrt(y): a.append((S.One, y**2)) elif y.is_Atom: a.append((y, S.One)) elif y.is_Pow and y.exp.is_integer: a.append((y, S.One)) else: raise NotImplementedError continue T, F = sift(y.args, is_sqrt, binary=True) a.append((Mul(*F), Mul(*T)**2)) a.sort(key=lambda z: z[1]) if a[-1][1] is S.One: # there are no surds return p surds = [z for y, z in a] for i in range(len(surds)): if surds[i] != 1: break g, b1, b2 = _split_gcd(*surds[i:]) a1 = [] a2 = [] for y, z in a: if z in b1: a1.append(y*z**S.Half) else: a2.append(y*z**S.Half) p1 = Add(*a1) p2 = Add(*a2) p = _mexpand(p1**2) - _mexpand(p2**2) return p
def test_factor_nc(): x, y = symbols('x,y') k = symbols('k', integer=True) n, m, o = symbols('n,m,o', commutative=False) # mul and multinomial expansion is needed from sympy.core.function import _mexpand e = x*(1 + y)**2 assert _mexpand(e) == x + x*2*y + x*y**2 def factor_nc_test(e): ex = _mexpand(e) assert ex.is_Add f = factor_nc(ex) assert not f.is_Add and _mexpand(f) == ex factor_nc_test(x*(1 + y)) factor_nc_test(n*(x + 1)) factor_nc_test(n*(x + m)) factor_nc_test((x + m)*n) factor_nc_test(n*m*(x*o + n*o*m)*n) s = Sum(x, (x, 1, 2)) factor_nc_test(x*(1 + s)) factor_nc_test(x*(1 + s)*s) factor_nc_test(x*(1 + sin(s))) factor_nc_test((1 + n)**2) factor_nc_test((x + n)*(x + m)*(x + y)) factor_nc_test(x*(n*m + 1)) factor_nc_test(x*(n*m + x)) factor_nc_test(x*(x*n*m + 1)) factor_nc_test(x*n*(x*m + 1)) factor_nc_test(x*(m*n + x*n*m)) factor_nc_test(n*(1 - m)*n**2) factor_nc_test((n + m)**2) factor_nc_test((n - m)*(n + m)**2) factor_nc_test((n + m)**2*(n - m)) factor_nc_test((m - n)*(n + m)**2*(n - m)) assert factor_nc(n*(n + n*m)) == n**2*(1 + m) assert factor_nc(m*(m*n + n*m*n**2)) == m*(m + n*m*n)*n eq = m*sin(n) - sin(n)*m assert factor_nc(eq) == eq # for coverage: from sympy.physics.secondquant import Commutator from sympy import factor eq = 1 + x*Commutator(m, n) assert factor_nc(eq) == eq eq = x*Commutator(m, n) + x*Commutator(m, o)*Commutator(m, n) assert factor(eq) == x*(1 + Commutator(m, o))*Commutator(m, n) # issue 6534 assert (2*n + 2*m).factor() == 2*(n + m) # issue 6701 assert factor_nc(n**k + n**(k + 1)) == n**k*(1 + n) assert factor_nc((m*n)**k + (m*n)**(k + 1)) == (1 + m*n)*(m*n)**k # issue 6918 assert factor_nc(-n*(2*x**2 + 2*x)) == -2*n*x*(x + 1)
def roots_quartic(f): r""" Returns a list of roots of a quartic polynomial. There are many references for solving quartic expressions available [1-5]. This reviewer has found that many of them require one to select from among 2 or more possible sets of solutions and that some solutions work when one is searching for real roots but don't work when searching for complex roots (though this is not always stated clearly). The following routine has been tested and found to be correct for 0, 2 or 4 complex roots. The quasisymmetric case solution [6] looks for quartics that have the form `x**4 + A*x**3 + B*x**2 + C*x + D = 0` where `(C/A)**2 = D`. Although no general solution that is always applicable for all coefficients is known to this reviewer, certain conditions are tested to determine the simplest 4 expressions that can be returned: 1) `f = c + a*(a**2/8 - b/2) == 0` 2) `g = d - a*(a*(3*a**2/256 - b/16) + c/4) = 0` 3) if `f != 0` and `g != 0` and `p = -d + a*c/4 - b**2/12` then a) `p == 0` b) `p != 0` Examples ======== >>> from sympy import Poly, symbols, I >>> from sympy.polys.polyroots import roots_quartic >>> r = roots_quartic(Poly('x**4-6*x**3+17*x**2-26*x+20')) >>> # 4 complex roots: 1+-I*sqrt(3), 2+-I >>> sorted(str(tmp.evalf(n=2)) for tmp in r) ['1.0 + 1.7*I', '1.0 - 1.7*I', '2.0 + 1.0*I', '2.0 - 1.0*I'] References ========== 1. http://mathforum.org/dr.math/faq/faq.cubic.equations.html 2. https://en.wikipedia.org/wiki/Quartic_function#Summary_of_Ferrari.27s_method 3. http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html 4. http://staff.bath.ac.uk/masjhd/JHD-CA.pdf 5. http://www.albmath.org/files/Math_5713.pdf 6. http://www.statemaster.com/encyclopedia/Quartic-equation 7. eqworld.ipmnet.ru/en/solutions/ae/ae0108.pdf """ _, a, b, c, d = f.monic().all_coeffs() if not d: return [S.Zero] + roots([1, a, b, c], multiple=True) elif (c / a)**2 == d: x, m = f.gen, c / a g = Poly(x**2 + a * x + b - 2 * m, x) z1, z2 = roots_quadratic(g) h1 = Poly(x**2 - z1 * x + m, x) h2 = Poly(x**2 - z2 * x + m, x) r1 = roots_quadratic(h1) r2 = roots_quadratic(h2) return r1 + r2 else: a2 = a**2 e = b - 3 * a2 / 8 f = _mexpand(c + a * (a2 / 8 - b / 2)) g = _mexpand(d - a * (a * (3 * a2 / 256 - b / 16) + c / 4)) aon4 = a / 4 if f is S.Zero: y1, y2 = [sqrt(tmp) for tmp in roots([1, e, g], multiple=True)] return [tmp - aon4 for tmp in [-y1, -y2, y1, y2]] if g is S.Zero: y = [S.Zero] + roots([1, 0, e, f], multiple=True) return [tmp - aon4 for tmp in y] else: # Descartes-Euler method, see [7] sols = _roots_quartic_euler(e, f, g, aon4) if sols: return sols # Ferrari method, see [1, 2] a2 = a**2 e = b - 3 * a2 / 8 f = c + a * (a2 / 8 - b / 2) g = d - a * (a * (3 * a2 / 256 - b / 16) + c / 4) p = -e**2 / 12 - g q = -e**3 / 108 + e * g / 3 - f**2 / 8 TH = Rational(1, 3) def _ans(y): w = sqrt(e + 2 * y) arg1 = 3 * e + 2 * y arg2 = 2 * f / w ans = [] for s in [-1, 1]: root = sqrt(-(arg1 + s * arg2)) for t in [-1, 1]: ans.append((s * w - t * root) / 2 - aon4) return ans # p == 0 case y1 = -5 * e / 6 - q**TH if p.is_zero: return _ans(y1) # if p != 0 then u below is not 0 root = sqrt(q**2 / 4 + p**3 / 27) r = -q / 2 + root # or -q/2 - root u = r**TH # primary root of solve(x**3 - r, x) y2 = -5 * e / 6 + u - p / u / 3 if fuzzy_not(p.is_zero): return _ans(y2) # sort it out once they know the values of the coefficients return [ Piecewise((a1, Eq(p, 0)), (a2, True)) for a1, a2 in zip(_ans(y1), _ans(y2)) ]
def minimal_polynomial(ex, x=None, **args): """ Computes the minimal polynomial of an algebraic element. Parameters ========== ex : algebraic element expression x : independent variable of the minimal polynomial Options ======= compose : if ``True`` ``_minpoly_compose`` is used, if ``False`` the ``groebner`` algorithm polys : if ``True`` returns a ``Poly`` object domain : ground domain Notes ===== By default ``compose=True``, the minimal polynomial of the subexpressions of ``ex`` are computed, then the arithmetic operations on them are performed using the resultant and factorization. If ``compose=False``, a bottom-up algorithm is used with ``groebner``. The default algorithm stalls less frequently. If no ground domain is given, it will be generated automatically from the expression. Examples ======== >>> from sympy import minimal_polynomial, sqrt, solve, QQ >>> from sympy.abc import x, y >>> minimal_polynomial(sqrt(2), x) x**2 - 2 >>> minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) x - sqrt(2) >>> minimal_polynomial(sqrt(2) + sqrt(3), x) x**4 - 10*x**2 + 1 >>> minimal_polynomial(solve(x**3 + x + 3)[0], x) x**3 + x + 3 >>> minimal_polynomial(sqrt(y), x) x**2 - y """ from sympy.polys.polytools import degree from sympy.polys.domains import FractionField from sympy.core.basic import preorder_traversal compose = args.get('compose', True) polys = args.get('polys', False) dom = args.get('domain', None) ex = sympify(ex) if ex.is_number: # not sure if it's always needed but try it for numbers (issue 8354) ex = _mexpand(ex, recursive=True) for expr in preorder_traversal(ex): if expr.is_AlgebraicNumber: compose = False break if x is not None: x, cls = sympify(x), Poly else: x, cls = Dummy('x'), PurePoly if not dom: dom = FractionField(QQ, list( ex.free_symbols)) if ex.free_symbols else QQ if hasattr(dom, 'symbols') and x in dom.symbols: raise GeneratorsError( "the variable %s is an element of the ground domain %s" % (x, dom)) if compose: result = _minpoly_compose(ex, x, dom) result = result.primitive()[1] c = result.coeff(x**degree(result, x)) if c.is_negative: result = expand_mul(-result) return cls(result, x, field=True) if polys else result.collect(x) if not dom.is_QQ: raise NotImplementedError("groebner method only works for QQ") result = _minpoly_groebner(ex, x, cls) return cls(result, x, field=True) if polys else result.collect(x)
def handle(expr): # Handle first reduces to the case # expr = 1/d, where d is an add, or d is base**p/2. # We do this by recursively calling handle on each piece. from sympy.simplify.simplify import nsimplify n, d = fraction(expr) if expr.is_Atom or (d.is_Atom and n.is_Atom): return expr elif not n.is_Atom: n = n.func(*[handle(a) for a in n.args]) return _unevaluated_Mul(n, handle(1/d)) elif n is not S.One: return _unevaluated_Mul(n, handle(1/d)) elif d.is_Mul: return _unevaluated_Mul(*[handle(1/d) for d in d.args]) # By this step, expr is 1/d, and d is not a mul. if not symbolic and d.free_symbols: return expr if ispow2(d): d2 = sqrtdenest(sqrt(d.base))**numer(d.exp) if d2 != d: return handle(1/d2) elif d.is_Pow and (d.exp.is_integer or d.base.is_positive): # (1/d**i) = (1/d)**i return handle(1/d.base)**d.exp if not (d.is_Add or ispow2(d)): return 1/d.func(*[handle(a) for a in d.args]) # handle 1/d treating d as an Add (though it may not be) keep = True # keep changes that are made # flatten it and collect radicals after checking for special # conditions d = _mexpand(d) # did it change? if d.is_Atom: return 1/d # is it a number that might be handled easily? if d.is_number: _d = nsimplify(d) if _d.is_Number and _d.equals(d): return 1/_d while True: # collect similar terms collected = defaultdict(list) for m in Add.make_args(d): # d might have become non-Add p2 = [] other = [] for i in Mul.make_args(m): if ispow2(i, log2=True): p2.append(i.base if i.exp is S.Half else i.base**(2*i.exp)) elif i is S.ImaginaryUnit: p2.append(S.NegativeOne) else: other.append(i) collected[tuple(ordered(p2))].append(Mul(*other)) rterms = list(ordered(list(collected.items()))) rterms = [(Mul(*i), Add(*j)) for i, j in rterms] nrad = len(rterms) - (1 if rterms[0][0] is S.One else 0) if nrad < 1: break elif nrad > max_terms: # there may have been invalid operations leading to this point # so don't keep changes, e.g. this expression is troublesome # in collecting terms so as not to raise the issue of 2834: # r = sqrt(sqrt(5) + 5) # eq = 1/(sqrt(5)*r + 2*sqrt(5)*sqrt(-sqrt(5) + 5) + 5*r) keep = False break if len(rterms) > 4: # in general, only 4 terms can be removed with repeated squaring # but other considerations can guide selection of radical terms # so that radicals are removed if all([x.is_Integer and (y**2).is_Rational for x, y in rterms]): nd, d = rad_rationalize(S.One, Add._from_args( [sqrt(x)*y for x, y in rterms])) n *= nd else: # is there anything else that might be attempted? keep = False break from sympy.simplify.powsimp import powsimp, powdenest num = powsimp(_num(rterms)) n *= num d *= num d = powdenest(_mexpand(d), force=symbolic) if d.is_Atom: break if not keep: return expr return _unevaluated_Mul(n, 1/d)
def linsolve(system, *symbols): if not system: return S.EmptySet # If second argument is an iterable if symbols and hasattr(symbols[0], '__iter__'): symbols = symbols[0] sym_gen = isinstance(symbols, GeneratorType) swap = {} b = None # if we don't get b the input was bad syms_needed_msg = None # unpack system if hasattr(system, '__iter__'): # 1). (A, b) if len(system) == 2 and isinstance(system[0], Matrix): A, b = system # 2). (eq1, eq2, ...) if not isinstance(system[0], Matrix): if sym_gen or not symbols: raise ValueError( filldedent(''' When passing a system of equations, the explicit symbols for which a solution is being sought must be given as a sequence, too. ''')) system = [ _mexpand(i.lhs - i.rhs if isinstance(i, Eq) else i, recursive=True) for i in system ] system, symbols, swap = recast_to_symbols(system, symbols) A, b = linear_eq_to_matrix(system, symbols) syms_needed_msg = 'free symbols in the equations provided' elif isinstance(system, Matrix) and not (symbols and not isinstance( symbols, GeneratorType) and isinstance(symbols[0], Matrix)): # 3). A augmented with b A, b = system[:, :-1], system[:, -1:] if b is None: raise ValueError("Invalid arguments") syms_needed_msg = syms_needed_msg or 'columns of A' if sym_gen: symbols = [next(symbols) for i in range(A.cols)] if any(set(symbols) & (A.free_symbols | b.free_symbols)): raise ValueError( filldedent(''' At least one of the symbols provided already appears in the system to be solved. One way to avoid this is to use Dummy symbols in the generator, e.g. numbered_symbols('%s', cls=Dummy) ''' % symbols[0].name.rstrip('1234567890'))) try: solution, params, free_syms = A.gauss_jordan_solve(b, freevar=True) except ValueError: # No solution return S.EmptySet # Replace free parameters with free symbols if params: if not symbols: symbols = [_ for _ in params] # re-use the parameters but put them in order # params [x, y, z] # free_symbols [2, 0, 4] # idx [1, 0, 2] idx = list(zip(*sorted(zip(free_syms, range(len(free_syms))))))[1] # simultaneous replacements {y: x, x: y, z: z} replace_dict = dict(zip(symbols, [symbols[i] for i in idx])) elif len(symbols) >= A.cols: replace_dict = { v: symbols[free_syms[k]] for k, v in enumerate(params) } else: raise IndexError( filldedent(''' the number of symbols passed should have a length equal to the number of %s. ''' % syms_needed_msg)) solution = [sol.xreplace(replace_dict) for sol in solution] solution = [(sol).xreplace(swap) for sol in solution] return FiniteSet(tuple(solution))
def minimal_polynomial(ex, x=None, **args): """ Computes the minimal polynomial of an algebraic element. Parameters ========== ex : algebraic element expression x : independent variable of the minimal polynomial Options ======= compose : if ``True`` ``_minpoly_compose`` is used, if ``False`` the ``groebner`` algorithm polys : if ``True`` returns a ``Poly`` object domain : ground domain Notes ===== By default ``compose=True``, the minimal polynomial of the subexpressions of ``ex`` are computed, then the arithmetic operations on them are performed using the resultant and factorization. If ``compose=False``, a bottom-up algorithm is used with ``groebner``. The default algorithm stalls less frequently. If no ground domain is given, it will be generated automatically from the expression. Examples ======== >>> from sympy import minimal_polynomial, sqrt, solve, QQ >>> from sympy.abc import x, y >>> minimal_polynomial(sqrt(2), x) x**2 - 2 >>> minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) x - sqrt(2) >>> minimal_polynomial(sqrt(2) + sqrt(3), x) x**4 - 10*x**2 + 1 >>> minimal_polynomial(solve(x**3 + x + 3)[0], x) x**3 + x + 3 >>> minimal_polynomial(sqrt(y), x) x**2 - y """ from sympy.polys.polytools import degree from sympy.polys.domains import FractionField from sympy.core.basic import preorder_traversal compose = args.get('compose', True) polys = args.get('polys', False) dom = args.get('domain', None) ex = sympify(ex) if ex.is_number: # not sure if it's always needed but try it for numbers (issue 8354) ex = _mexpand(ex, recursive=True) for expr in preorder_traversal(ex): if expr.is_AlgebraicNumber: compose = False break if x is not None: x, cls = sympify(x), Poly else: x, cls = Dummy('x'), PurePoly if not dom: dom = FractionField(QQ, list(ex.free_symbols)) if ex.free_symbols else QQ if hasattr(dom, 'symbols') and x in dom.symbols: raise GeneratorsError("the variable %s is an element of the ground domain %s" % (x, dom)) if compose: result = _minpoly_compose(ex, x, dom) result = result.primitive()[1] c = result.coeff(x**degree(result, x)) if c.is_negative: result = expand_mul(-result) return cls(result, x, field=True) if polys else result.collect(x) if not dom.is_QQ: raise NotImplementedError("groebner method only works for QQ") result = _minpoly_groebner(ex, x, cls) return cls(result, x, field=True) if polys else result.collect(x)
def bivariate_type(f, x, y, **kwargs): """Given an expression, f, 3 tests will be done to see what type of composite bivariate it might be, options for u(x, y) are:: x*y x+y x*y+x x*y+y If it matches one of these types, ``u(x, y)``, ``P(u)`` and dummy variable ``u`` will be returned. Solving ``P(u)`` for ``u`` and equating the solutions to ``u(x, y)`` and then solving for ``x`` or ``y`` is equivalent to solving the original expression for ``x`` or ``y``. If ``x`` and ``y`` represent two functions in the same variable, e.g. ``x = g(t)`` and ``y = h(t)``, then if ``u(x, y) - p`` can be solved for ``t`` then these represent the solutions to ``P(u) = 0`` when ``p`` are the solutions of ``P(u) = 0``. Only positive values of ``u`` are considered. Examples ======== >>> from sympy.solvers.solvers import solve >>> from sympy.solvers.bivariate import bivariate_type >>> from sympy.abc import x, y >>> eq = (x**2 - 3).subs(x, x + y) >>> bivariate_type(eq, x, y) (x + y, _u**2 - 3, _u) >>> uxy, pu, u = _ >>> usol = solve(pu, u); usol [sqrt(3)] >>> [solve(uxy - s) for s in solve(pu, u)] [[{x: -y + sqrt(3)}]] >>> all(eq.subs(s).equals(0) for sol in _ for s in sol) True """ u = Dummy('u', positive=True) if kwargs.pop('first', True): p = Poly(f, x, y) f = p.as_expr() _x = Dummy() _y = Dummy() rv = bivariate_type(Poly(f.subs({x: _x, y: _y}), _x, _y), _x, _y, first=False) if rv: reps = {_x: x, _y: y} return rv[0].xreplace(reps), rv[1].xreplace(reps), rv[2] return p = f f = p.as_expr() # f(x*y) args = Add.make_args(p.as_expr()) new = [] for a in args: a = _mexpand(a.subs(x, u/y)) free = a.free_symbols if x in free or y in free: break new.append(a) else: return x*y, Add(*new), u def ok(f, v, c): new = _mexpand(f.subs(v, c)) free = new.free_symbols return None if (x in free or y in free) else new # f(a*x + b*y) new = [] d = p.degree(x) if p.degree(y) == d: a = root(p.coeff_monomial(x**d), d) b = root(p.coeff_monomial(y**d), d) new = ok(f, x, (u - b*y)/a) if new is not None: return a*x + b*y, new, u # f(a*x*y + b*y) new = [] d = p.degree(x) if p.degree(y) == d: for itry in range(2): a = root(p.coeff_monomial(x**d*y**d), d) b = root(p.coeff_monomial(y**d), d) new = ok(f, x, (u - b*y)/a/y) if new is not None: return a*x*y + b*y, new, u x, y = y, x
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 _sqrt_match(p): """Return [a, b, r] for p.match(a + b*sqrt(r)) where, in addition to matching, sqrt(r) also has then maximal sqrt_depth among addends of p. Examples ======== >>> from sympy.functions.elementary.miscellaneous import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match >>> _sqrt_match(1 + sqrt(2) + sqrt(2)*sqrt(3) + 2*sqrt(1+sqrt(5))) [1 + sqrt(2) + sqrt(6), 2, 1 + sqrt(5)] """ from sympy.simplify.simplify import split_surds p = _mexpand(p) if p.is_Number: res = (p, S.Zero, S.Zero) elif p.is_Add: pargs = sorted(p.args, key=default_sort_key) if all((x**2).is_Rational for x in pargs): r, b, a = split_surds(p) res = a, b, r return list(res) # to make the process canonical, the argument is included in the tuple # so when the max is selected, it will be the largest arg having a # given depth v = [(sqrt_depth(x), x, i) for i, x in enumerate(pargs)] nmax = max(v, key=default_sort_key) if nmax[0] == 0: res = [] else: # select r depth, _, i = nmax r = pargs.pop(i) v.pop(i) b = S.One if r.is_Mul: bv = [] rv = [] for x in r.args: if sqrt_depth(x) < depth: bv.append(x) else: rv.append(x) b = Mul._from_args(bv) r = Mul._from_args(rv) # collect terms comtaining r a1 = [] b1 = [b] for x in v: if x[0] < depth: a1.append(x[1]) else: x1 = x[1] if x1 == r: b1.append(1) else: if x1.is_Mul: x1args = list(x1.args) if r in x1args: x1args.remove(r) b1.append(Mul(*x1args)) else: a1.append(x[1]) else: a1.append(x[1]) a = Add(*a1) b = Add(*b1) #a = Add._from_args(pargs) res = (a, b, r**2) else: b, r = p.as_coeff_Mul() if is_sqrt(r): res = (S.Zero, b, r**2) else: res = [] return list(res)
def diop_simplify(eq): return _mexpand(powsimp(_mexpand(eq)))
def _sqrt_symbolic_denest(a, b, r): """Given an expression, sqrt(a + b*sqrt(b)), return the denested expression or None. Explanation =========== If r = ra + rb*sqrt(rr), try replacing sqrt(rr) in ``a`` with (y**2 - ra)/rb, and if the result is a quadratic, ca*y**2 + cb*y + cc, and (cb + b)**2 - 4*ca*cc is 0, then sqrt(a + b*sqrt(r)) can be rewritten as sqrt(ca*(sqrt(r) + (cb + b)/(2*ca))**2). Examples ======== >>> from sympy.simplify.sqrtdenest import _sqrt_symbolic_denest, sqrtdenest >>> from sympy import sqrt, Symbol >>> from sympy.abc import x >>> a, b, r = 16 - 2*sqrt(29), 2, -10*sqrt(29) + 55 >>> _sqrt_symbolic_denest(a, b, r) sqrt(11 - 2*sqrt(29)) + sqrt(5) If the expression is numeric, it will be simplified: >>> w = sqrt(sqrt(sqrt(3) + 1) + 1) + 1 + sqrt(2) >>> sqrtdenest(sqrt((w**2).expand())) 1 + sqrt(2) + sqrt(1 + sqrt(1 + sqrt(3))) Otherwise, it will only be simplified if assumptions allow: >>> w = w.subs(sqrt(3), sqrt(x + 3)) >>> sqrtdenest(sqrt((w**2).expand())) sqrt((sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2))**2) Notice that the argument of the sqrt is a square. If x is made positive then the sqrt of the square is resolved: >>> _.subs(x, Symbol('x', positive=True)) sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2) """ a, b, r = map(sympify, (a, b, r)) rval = _sqrt_match(r) if not rval: return None ra, rb, rr = rval if rb: y = Dummy('y', positive=True) try: newa = Poly(a.subs(sqrt(rr), (y**2 - ra)/rb), y) except PolynomialError: return None if newa.degree() == 2: ca, cb, cc = newa.all_coeffs() cb += b if _mexpand(cb**2 - 4*ca*cc).equals(0): z = sqrt(ca*(sqrt(r) + cb/(2*ca))**2) if z.is_number: z = _mexpand(Mul._from_args(z.as_content_primitive())) return z
def _separate_sq(p): """ helper function for ``_minimal_polynomial_sq`` It selects a rational ``g`` such that the polynomial ``p`` consists of a sum of terms whose surds squared have gcd equal to ``g`` and a sum of terms with surds squared prime with ``g``; then it takes the field norm to eliminate ``sqrt(g)`` See simplify.simplify.split_surds and polytools.sqf_norm. Examples ======== >>> from sympy import sqrt >>> from sympy.abc import x >>> from sympy.polys.numberfields import _separate_sq >>> p= -x + sqrt(2) + sqrt(3) + sqrt(7) >>> p = _separate_sq(p); p -x**2 + 2*sqrt(3)*x + 2*sqrt(7)*x - 2*sqrt(21) - 8 >>> p = _separate_sq(p); p -x**4 + 4*sqrt(7)*x**3 - 32*x**2 + 8*sqrt(7)*x + 20 >>> p = _separate_sq(p); p -x**8 + 48*x**6 - 536*x**4 + 1728*x**2 - 400 """ from sympy.utilities.iterables import sift def is_sqrt(expr): return expr.is_Pow and expr.exp is S.Half # p = c1*sqrt(q1) + ... + cn*sqrt(qn) -> a = [(c1, q1), .., (cn, qn)] a = [] for y in p.args: if not y.is_Mul: if is_sqrt(y): a.append((S.One, y**2)) elif y.is_Atom: a.append((y, S.One)) elif y.is_Pow and y.exp.is_integer: a.append((y, S.One)) else: raise NotImplementedError continue sifted = sift(y.args, is_sqrt) a.append((Mul(*sifted[False]), Mul(*sifted[True])**2)) a.sort(key=lambda z: z[1]) if a[-1][1] is S.One: # there are no surds return p surds = [z for y, z in a] for i in range(len(surds)): if surds[i] != 1: break g, b1, b2 = _split_gcd(*surds[i:]) a1 = [] a2 = [] for y, z in a: if z in b1: a1.append(y * z**S.Half) else: a2.append(y * z**S.Half) p1 = Add(*a1) p2 = Add(*a2) p = _mexpand(p1**2) - _mexpand(p2**2) return p
def _denester(nested, av0, h, max_depth_level): """Denests a list of expressions that contain nested square roots. Algorithm based on <http://www.almaden.ibm.com/cs/people/fagin/symb85.pdf>. It is assumed that all of the elements of 'nested' share the same bottom-level radicand. (This is stated in the paper, on page 177, in the paragraph immediately preceding the algorithm.) When evaluating all of the arguments in parallel, the bottom-level radicand only needs to be denested once. This means that calling _denester with x arguments results in a recursive invocation with x+1 arguments; hence _denester has polynomial complexity. However, if the arguments were evaluated separately, each call would result in two recursive invocations, and the algorithm would have exponential complexity. This is discussed in the paper in the middle paragraph of page 179. """ from sympy.simplify.simplify import radsimp if h > max_depth_level: return None, None if av0[1] is None: return None, None if (av0[0] is None and all(n.is_Number for n in nested)): # no arguments are nested for f in _subsets(len(nested)): # test subset 'f' of nested p = _mexpand(Mul(*[nested[i] for i in range(len(f)) if f[i]])) if f.count(1) > 1 and f[-1]: p = -p sqp = sqrt(p) if sqp.is_Rational: return sqp, f # got a perfect square so return its square root. # Otherwise, return the radicand from the previous invocation. return sqrt(nested[-1]), [0]*len(nested) else: R = None if av0[0] is not None: values = [av0[:2]] R = av0[2] nested2 = [av0[3], R] av0[0] = None else: values = list(filter(None, [_sqrt_match(expr) for expr in nested])) for v in values: if v[2]: # Since if b=0, r is not defined if R is not None: if R != v[2]: av0[1] = None return None, None else: R = v[2] if R is None: # return the radicand from the previous invocation return sqrt(nested[-1]), [0]*len(nested) nested2 = [_mexpand(v[0]**2) - _mexpand(R*v[1]**2) for v in values] + [R] d, f = _denester(nested2, av0, h + 1, max_depth_level) #d = sqrtdenest(d) if not f: return None, None if not any(f[i] for i in range(len(nested))): v = values[-1] return sqrt(v[0] + _mexpand(v[1]*d)), f else: p = Mul(*[nested[i] for i in range(len(nested)) if f[i]]) v = _sqrt_match(p) if 1 in f and f.index(1) < len(nested) - 1 and f[len(nested) - 1]: v[0] = -v[0] v[1] = -v[1] if not f[len(nested)]: # Solution denests with square roots vad = _mexpand(v[0] + d) if vad <= 0: # return the radicand from the previous invocation. return sqrt(nested[-1]), [0]*len(nested) if not(sqrt_depth(vad) <= sqrt_depth(R) + 1 or (vad**2).is_Number): av0[1] = None return None, None sqvad = _sqrtdenest1(sqrt(vad), denester=False) if not (sqrt_depth(sqvad) <= sqrt_depth(R) + 1): av0[1] = None return None, None sqvad1 = radsimp(1/sqvad) res = _mexpand(sqvad/sqrt(2) + (v[1]*sqrt(R)*sqvad1/sqrt(2))) return res, f # sign(v[1])*sqrt(_mexpand(v[1]**2*R*vad1/2))), f else: # Solution requires a fourth root s2 = _mexpand(v[1]*R) + d if s2 <= 0: return sqrt(nested[-1]), [0]*len(nested) FR, s = root(_mexpand(R), 4), sqrt(s2) return _mexpand(s/(sqrt(2)*FR) + v[0]*FR/(sqrt(2)*s)), f
def bivariate_type(f, x, y, **kwargs): """Given an expression, f, 3 tests will be done to see what type of composite bivariate it might be, options for u(x, y) are:: x*y x+y x*y+x x*y+y If it matches one of these types, ``u(x, y)``, ``P(u)`` and dummy variable ``u`` will be returned. Solving ``P(u)`` for ``u`` and equating the solutions to ``u(x, y)`` and then solving for ``x`` or ``y`` is equivalent to solving the original expression for ``x`` or ``y``. If ``x`` and ``y`` represent two functions in the same variable, e.g. ``x = g(t)`` and ``y = h(t)``, then if ``u(x, y) - p`` can be solved for ``t`` then these represent the solutions to ``P(u) = 0`` when ``p`` are the solutions of ``P(u) = 0``. Only positive values of ``u`` are considered. Examples ======== >>> from sympy.solvers.solvers import solve >>> from sympy.solvers.bivariate import bivariate_type >>> from sympy.abc import x, y >>> eq = (x**2 - 3).subs(x, x + y) >>> bivariate_type(eq, x, y) (x + y, _u**2 - 3, _u) >>> uxy, pu, u = _ >>> usol = solve(pu, u); usol [sqrt(3)] >>> [solve(uxy - s) for s in solve(pu, u)] [[{x: -y + sqrt(3)}]] >>> all(eq.subs(s).equals(0) for sol in _ for s in sol) True """ u = Dummy('u', positive=True) if kwargs.pop('first', True): p = Poly(f, x, y) f = p.as_expr() _x = Dummy() _y = Dummy() rv = bivariate_type(Poly(f.subs({ x: _x, y: _y }), _x, _y), _x, _y, first=False) if rv: reps = {_x: x, _y: y} return rv[0].xreplace(reps), rv[1].xreplace(reps), rv[2] return p = f f = p.as_expr() # f(x*y) args = Add.make_args(p.as_expr()) new = [] for a in args: a = _mexpand(a.subs(x, u / y)) free = a.free_symbols if x in free or y in free: break new.append(a) else: return x * y, Add(*new), u def ok(f, v, c): new = _mexpand(f.subs(v, c)) free = new.free_symbols return None if (x in free or y in free) else new # f(a*x + b*y) new = [] d = p.degree(x) if p.degree(y) == d: a = root(p.coeff_monomial(x**d), d) b = root(p.coeff_monomial(y**d), d) new = ok(f, x, (u - b * y) / a) if new is not None: return a * x + b * y, new, u # f(a*x*y + b*y) new = [] d = p.degree(x) if p.degree(y) == d: for itry in range(2): a = root(p.coeff_monomial(x**d * y**d), d) b = root(p.coeff_monomial(y**d), d) new = ok(f, x, (u - b * y) / a / y) if new is not None: return a * x * y + b * y, new, u x, y = y, x
def ok(f, v, c): new = _mexpand(f.subs(v, c)) free = new.free_symbols return None if (x in free or y in free) else new
def test_mexpand(): from sympy.abc import x assert _mexpand(None) is None assert _mexpand(1) is S.One assert _mexpand(x * (x + 1)**2) == (x * (x + 1)**2).expand()
def _sqrt_match(p): """Return [a, b, r] for p.match(a + b*sqrt(r)) where, in addition to matching, sqrt(r) also has then maximal sqrt_depth among addends of p. Examples ======== >>> from sympy.functions.elementary.miscellaneous import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match >>> _sqrt_match(1 + sqrt(2) + sqrt(2)*sqrt(3) + 2*sqrt(1+sqrt(5))) [1 + sqrt(2) + sqrt(6), 2, 1 + sqrt(5)] """ from sympy.simplify.radsimp import split_surds p = _mexpand(p) if p.is_Number: res = (p, S.Zero, S.Zero) elif p.is_Add: pargs = sorted(p.args, key=default_sort_key) if all((x**2).is_Rational for x in pargs): r, b, a = split_surds(p) res = a, b, r return list(res) # to make the process canonical, the argument is included in the tuple # so when the max is selected, it will be the largest arg having a # given depth v = [(sqrt_depth(x), x, i) for i, x in enumerate(pargs)] nmax = max(v, key=default_sort_key) if nmax[0] == 0: res = [] else: # select r depth, _, i = nmax r = pargs.pop(i) v.pop(i) b = S.One if r.is_Mul: bv = [] rv = [] for x in r.args: if sqrt_depth(x) < depth: bv.append(x) else: rv.append(x) b = Mul._from_args(bv) r = Mul._from_args(rv) # collect terms comtaining r a1 = [] b1 = [b] for x in v: if x[0] < depth: a1.append(x[1]) else: x1 = x[1] if x1 == r: b1.append(1) else: if x1.is_Mul: x1args = list(x1.args) if r in x1args: x1args.remove(r) b1.append(Mul(*x1args)) else: a1.append(x[1]) else: a1.append(x[1]) a = Add(*a1) b = Add(*b1) res = (a, b, r**2) else: b, r = p.as_coeff_Mul() if is_sqrt(r): res = (S.Zero, b, r**2) else: res = [] return list(res)
def test_mexpand(): from sympy.abc import x assert _mexpand(None) is None assert _mexpand(1) is S.One assert _mexpand(x*(x + 1)**2) == (x*(x + 1)**2).expand()
def sqrt_biquadratic_denest(expr, a, b, r, d2): """denest expr = sqrt(a + b*sqrt(r)) where a, b, r are linear combinations of square roots of positive rationals on the rationals (SQRR) and r > 0, b != 0, d2 = a**2 - b**2*r > 0 If it cannot denest it returns None. ALGORITHM Search for a solution A of type SQRR of the biquadratic equation 4*A**4 - 4*a*A**2 + b**2*r = 0 (1) sqd = sqrt(a**2 - b**2*r) Choosing the sqrt to be positive, the possible solutions are A = sqrt(a/2 +/- sqd/2) Since a, b, r are SQRR, then a**2 - b**2*r is a SQRR, so if sqd can be denested, it is done by _sqrtdenest_rec, and the result is a SQRR. Similarly for A. Examples of solutions (in both cases a and sqd are positive): Example of expr with solution sqrt(a/2 + sqd/2) but not solution sqrt(a/2 - sqd/2): expr = sqrt(-sqrt(15) - sqrt(2)*sqrt(-sqrt(5) + 5) - sqrt(3) + 8) a = -sqrt(15) - sqrt(3) + 8; sqd = -2*sqrt(5) - 2 + 4*sqrt(3) Example of expr with solution sqrt(a/2 - sqd/2) but not solution sqrt(a/2 + sqd/2): w = 2 + r2 + r3 + (1 + r3)*sqrt(2 + r2 + 5*r3) expr = sqrt((w**2).expand()) a = 4*sqrt(6) + 8*sqrt(2) + 47 + 28*sqrt(3) sqd = 29 + 20*sqrt(3) Define B = b/2*A; eq.(1) implies a = A**2 + B**2*r; then expr**2 = a + b*sqrt(r) = (A + B*sqrt(r))**2 Examples ======== >>> from sympy import sqrt >>> from sympy.simplify.sqrtdenest import _sqrt_match, sqrt_biquadratic_denest >>> z = sqrt((2*sqrt(2) + 4)*sqrt(2 + sqrt(2)) + 5*sqrt(2) + 8) >>> a, b, r = _sqrt_match(z**2) >>> d2 = a**2 - b**2*r >>> sqrt_biquadratic_denest(z, a, b, r, d2) sqrt(2) + sqrt(sqrt(2) + 2) + 2 """ from sympy.simplify.radsimp import radsimp, rad_rationalize if r <= 0 or d2 < 0 or not b or sqrt_depth(expr.base) < 2: return None for x in (a, b, r): for y in x.args: y2 = y**2 if not y2.is_Integer or not y2.is_positive: return None sqd = _mexpand(sqrtdenest(sqrt(radsimp(d2)))) if sqrt_depth(sqd) > 1: return None x1, x2 = [a / 2 + sqd / 2, a / 2 - sqd / 2] # look for a solution A with depth 1 for x in (x1, x2): A = sqrtdenest(sqrt(x)) if sqrt_depth(A) > 1: continue Bn, Bd = rad_rationalize(b, _mexpand(2 * A)) B = Bn / Bd z = A + B * sqrt(r) if z < 0: z = -z return _mexpand(z) return None
def simplify(expr, ratio=1.7, measure=count_ops, fu=False): """ Simplifies the given expression. Simplification is not a well defined term and the exact strategies this function tries can change in the future versions of SymPy. If your algorithm relies on "simplification" (whatever it is), try to determine what you need exactly - is it powsimp()?, radsimp()?, together()?, logcombine()?, or something else? And use this particular function directly, because those are well defined and thus your algorithm will be robust. Nonetheless, especially for interactive use, or when you don't know anything about the structure of the expression, simplify() tries to apply intelligent heuristics to make the input expression "simpler". For example: >>> from sympy import simplify, cos, sin >>> from sympy.abc import x, y >>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2) >>> a (x**2 + x)/(x*sin(y)**2 + x*cos(y)**2) >>> simplify(a) x + 1 Note that we could have obtained the same result by using specific simplification functions: >>> from sympy import trigsimp, cancel >>> trigsimp(a) (x**2 + x)/x >>> cancel(_) x + 1 In some cases, applying :func:`simplify` may actually result in some more complicated expression. The default ``ratio=1.7`` prevents more extreme cases: if (result length)/(input length) > ratio, then input is returned unmodified. The ``measure`` parameter lets you specify the function used to determine how complex an expression is. The function should take a single argument as an expression and return a number such that if expression ``a`` is more complex than expression ``b``, then ``measure(a) > measure(b)``. The default measure function is :func:`count_ops`, which returns the total number of operations in the expression. For example, if ``ratio=1``, ``simplify`` output can't be longer than input. :: >>> from sympy import sqrt, simplify, count_ops, oo >>> root = 1/(sqrt(2)+3) Since ``simplify(root)`` would result in a slightly longer expression, root is returned unchanged instead:: >>> simplify(root, ratio=1) == root True If ``ratio=oo``, simplify will be applied anyway:: >>> count_ops(simplify(root, ratio=oo)) > count_ops(root) True Note that the shortest expression is not necessary the simplest, so setting ``ratio`` to 1 may not be a good idea. Heuristically, the default value ``ratio=1.7`` seems like a reasonable choice. You can easily define your own measure function based on what you feel should represent the "size" or "complexity" of the input expression. Note that some choices, such as ``lambda expr: len(str(expr))`` may appear to be good metrics, but have other problems (in this case, the measure function may slow down simplify too much for very large expressions). If you don't know what a good metric would be, the default, ``count_ops``, is a good one. For example: >>> from sympy import symbols, log >>> a, b = symbols('a b', positive=True) >>> g = log(a) + log(b) + log(a)*log(1/b) >>> h = simplify(g) >>> h log(a*b**(-log(a) + 1)) >>> count_ops(g) 8 >>> count_ops(h) 5 So you can see that ``h`` is simpler than ``g`` using the count_ops metric. However, we may not like how ``simplify`` (in this case, using ``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way to reduce this would be to give more weight to powers as operations in ``count_ops``. We can do this by using the ``visual=True`` option: >>> print(count_ops(g, visual=True)) 2*ADD + DIV + 4*LOG + MUL >>> print(count_ops(h, visual=True)) 2*LOG + MUL + POW + SUB >>> from sympy import Symbol, S >>> def my_measure(expr): ... POW = Symbol('POW') ... # Discourage powers by giving POW a weight of 10 ... count = count_ops(expr, visual=True).subs(POW, 10) ... # Every other operation gets a weight of 1 (the default) ... count = count.replace(Symbol, type(S.One)) ... return count >>> my_measure(g) 8 >>> my_measure(h) 14 >>> 15./8 > 1.7 # 1.7 is the default ratio True >>> simplify(g, measure=my_measure) -log(a)*log(b) + log(a) + log(b) Note that because ``simplify()`` internally tries many different simplification strategies and then compares them using the measure function, we get a completely different result that is still different from the input expression by doing this. """ expr = sympify(expr) try: return expr._eval_simplify(ratio=ratio, measure=measure) except AttributeError: pass original_expr = expr = signsimp(expr) from sympy.simplify.hyperexpand import hyperexpand from sympy.functions.special.bessel import BesselBase from sympy import Sum, Product if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack return expr if not isinstance(expr, (Add, Mul, Pow, ExpBase)): if isinstance(expr, Function) and hasattr(expr, "inverse"): if len(expr.args) == 1 and len(expr.args[0].args) == 1 and \ isinstance(expr.args[0], expr.inverse(argindex=1)): return simplify(expr.args[0].args[0], ratio=ratio, measure=measure, fu=fu) return expr.func(*[simplify(x, ratio=ratio, measure=measure, fu=fu) for x in expr.args]) # TODO: Apply different strategies, considering expression pattern: # is it a purely rational function? Is there any trigonometric function?... # See also https://github.com/sympy/sympy/pull/185. def shorter(*choices): '''Return the choice that has the fewest ops. In case of a tie, the expression listed first is selected.''' if not has_variety(choices): return choices[0] return min(choices, key=measure) expr = bottom_up(expr, lambda w: w.normal()) expr = Mul(*powsimp(expr).as_content_primitive()) _e = cancel(expr) expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829 expr2 = shorter(together(expr, deep=True), together(expr1, deep=True)) if ratio is S.Infinity: expr = expr2 else: expr = shorter(expr2, expr1, expr) if not isinstance(expr, Basic): # XXX: temporary hack return expr expr = factor_terms(expr, sign=False) # hyperexpand automatically only works on hypergeometric terms expr = hyperexpand(expr) expr = piecewise_fold(expr) if expr.has(BesselBase): expr = besselsimp(expr) if expr.has(TrigonometricFunction) and not fu or expr.has( HyperbolicFunction): expr = trigsimp(expr, deep=True) if expr.has(log): expr = shorter(expand_log(expr, deep=True), logcombine(expr)) if expr.has(CombinatorialFunction, gamma): expr = combsimp(expr) if expr.has(Sum): expr = sum_simplify(expr) if expr.has(Product): expr = product_simplify(expr) short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr) short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short))) if short.has(TrigonometricFunction, HyperbolicFunction, ExpBase): short = exptrigsimp(short, simplify=False) # get rid of hollow 2-arg Mul factorization hollow_mul = Transform( lambda x: Mul(*x.args), lambda x: x.is_Mul and len(x.args) == 2 and x.args[0].is_Number and x.args[1].is_Add and x.is_commutative) expr = short.xreplace(hollow_mul) numer, denom = expr.as_numer_denom() if denom.is_Add: n, d = fraction(radsimp(1/denom, symbolic=False, max_terms=1)) if n is not S.One: expr = (numer*n).expand()/d if expr.could_extract_minus_sign(): n, d = fraction(expr) if d != 0: expr = signsimp(-n/(-d)) if measure(expr) > ratio*measure(original_expr): expr = original_expr return expr
def _denester(nested, av0, h, max_depth_level): """Denests a list of expressions that contain nested square roots. Algorithm based on <http://www.almaden.ibm.com/cs/people/fagin/symb85.pdf>. It is assumed that all of the elements of 'nested' share the same bottom-level radicand. (This is stated in the paper, on page 177, in the paragraph immediately preceding the algorithm.) When evaluating all of the arguments in parallel, the bottom-level radicand only needs to be denested once. This means that calling _denester with x arguments results in a recursive invocation with x+1 arguments; hence _denester has polynomial complexity. However, if the arguments were evaluated separately, each call would result in two recursive invocations, and the algorithm would have exponential complexity. This is discussed in the paper in the middle paragraph of page 179. """ from sympy.simplify.simplify import radsimp if h > max_depth_level: return None, None if av0[1] is None: return None, None if (av0[0] is None and all(n.is_Number for n in nested)): # no arguments are nested for f in _subsets(len(nested)): # test subset 'f' of nested p = _mexpand(Mul(*[nested[i] for i in range(len(f)) if f[i]])) if f.count(1) > 1 and f[-1]: p = -p sqp = sqrt(p) if sqp.is_Rational: return sqp, f # got a perfect square so return its square root. # Otherwise, return the radicand from the previous invocation. return sqrt(nested[-1]), [0] * len(nested) else: R = None if av0[0] is not None: values = [av0[:2]] R = av0[2] nested2 = [av0[3], R] av0[0] = None else: values = list(filter(None, [_sqrt_match(expr) for expr in nested])) for v in values: if v[2]: # Since if b=0, r is not defined if R is not None: if R != v[2]: av0[1] = None return None, None else: R = v[2] if R is None: # return the radicand from the previous invocation return sqrt(nested[-1]), [0] * len(nested) nested2 = [ _mexpand(v[0]**2) - _mexpand(R * v[1]**2) for v in values ] + [R] d, f = _denester(nested2, av0, h + 1, max_depth_level) if not f: return None, None if not any(f[i] for i in range(len(nested))): v = values[-1] return sqrt(v[0] + _mexpand(v[1] * d)), f else: p = Mul(*[nested[i] for i in range(len(nested)) if f[i]]) v = _sqrt_match(p) if 1 in f and f.index(1) < len(nested) - 1 and f[len(nested) - 1]: v[0] = -v[0] v[1] = -v[1] if not f[len(nested)]: # Solution denests with square roots vad = _mexpand(v[0] + d) if vad <= 0: # return the radicand from the previous invocation. return sqrt(nested[-1]), [0] * len(nested) if not (sqrt_depth(vad) <= sqrt_depth(R) + 1 or (vad**2).is_Number): av0[1] = None return None, None sqvad = _sqrtdenest1(sqrt(vad), denester=False) if not (sqrt_depth(sqvad) <= sqrt_depth(R) + 1): av0[1] = None return None, None sqvad1 = radsimp(1 / sqvad) res = _mexpand(sqvad / sqrt(2) + (v[1] * sqrt(R) * sqvad1 / sqrt(2))) return res, f # sign(v[1])*sqrt(_mexpand(v[1]**2*R*vad1/2))), f else: # Solution requires a fourth root s2 = _mexpand(v[1] * R) + d if s2 <= 0: return sqrt(nested[-1]), [0] * len(nested) FR, s = root(_mexpand(R), 4), sqrt(s2) return _mexpand(s / (sqrt(2) * FR) + v[0] * FR / (sqrt(2) * s)), f
def roots_quartic(f): r""" Returns a list of roots of a quartic polynomial. There are many references for solving quartic expressions available [1-5]. This reviewer has found that many of them require one to select from among 2 or more possible sets of solutions and that some solutions work when one is searching for real roots but don't work when searching for complex roots (though this is not always stated clearly). The following routine has been tested and found to be correct for 0, 2 or 4 complex roots. The quasisymmetric case solution [6] looks for quartics that have the form `x**4 + A*x**3 + B*x**2 + C*x + D = 0` where `(C/A)**2 = D`. Although no general solution that is always applicable for all coefficients is known to this reviewer, certain conditions are tested to determine the simplest 4 expressions that can be returned: 1) `f = c + a*(a**2/8 - b/2) == 0` 2) `g = d - a*(a*(3*a**2/256 - b/16) + c/4) = 0` 3) if `f != 0` and `g != 0` and `p = -d + a*c/4 - b**2/12` then a) `p == 0` b) `p != 0` Examples ======== >>> from sympy import Poly, symbols, I >>> from sympy.polys.polyroots import roots_quartic >>> r = roots_quartic(Poly('x**4-6*x**3+17*x**2-26*x+20')) >>> # 4 complex roots: 1+-I*sqrt(3), 2+-I >>> sorted(str(tmp.evalf(n=2)) for tmp in r) ['1.0 + 1.7*I', '1.0 - 1.7*I', '2.0 + 1.0*I', '2.0 - 1.0*I'] References ========== 1. http://mathforum.org/dr.math/faq/faq.cubic.equations.html 2. https://en.wikipedia.org/wiki/Quartic_function#Summary_of_Ferrari.27s_method 3. http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html 4. http://staff.bath.ac.uk/masjhd/JHD-CA.pdf 5. http://www.albmath.org/files/Math_5713.pdf 6. http://www.statemaster.com/encyclopedia/Quartic-equation 7. eqworld.ipmnet.ru/en/solutions/ae/ae0108.pdf """ _, a, b, c, d = f.monic().all_coeffs() if not d: return [S.Zero] + roots([1, a, b, c], multiple=True) elif (c/a)**2 == d: x, m = f.gen, c/a g = Poly(x**2 + a*x + b - 2*m, x) z1, z2 = roots_quadratic(g) h1 = Poly(x**2 - z1*x + m, x) h2 = Poly(x**2 - z2*x + m, x) r1 = roots_quadratic(h1) r2 = roots_quadratic(h2) return r1 + r2 else: a2 = a**2 e = b - 3*a2/8 f = _mexpand(c + a*(a2/8 - b/2)) g = _mexpand(d - a*(a*(3*a2/256 - b/16) + c/4)) aon4 = a/4 if f is S.Zero: y1, y2 = [sqrt(tmp) for tmp in roots([1, e, g], multiple=True)] return [tmp - aon4 for tmp in [-y1, -y2, y1, y2]] if g is S.Zero: y = [S.Zero] + roots([1, 0, e, f], multiple=True) return [tmp - aon4 for tmp in y] else: # Descartes-Euler method, see [7] sols = _roots_quartic_euler(e, f, g, aon4) if sols: return sols # Ferrari method, see [1, 2] a2 = a**2 e = b - 3*a2/8 f = c + a*(a2/8 - b/2) g = d - a*(a*(3*a2/256 - b/16) + c/4) p = -e**2/12 - g q = -e**3/108 + e*g/3 - f**2/8 TH = Rational(1, 3) def _ans(y): w = sqrt(e + 2*y) arg1 = 3*e + 2*y arg2 = 2*f/w ans = [] for s in [-1, 1]: root = sqrt(-(arg1 + s*arg2)) for t in [-1, 1]: ans.append((s*w - t*root)/2 - aon4) return ans # p == 0 case y1 = -5*e/6 - q**TH if p.is_zero: return _ans(y1) # if p != 0 then u below is not 0 root = sqrt(q**2/4 + p**3/27) r = -q/2 + root # or -q/2 - root u = r**TH # primary root of solve(x**3 - r, x) y2 = -5*e/6 + u - p/u/3 if fuzzy_not(p.is_zero): return _ans(y2) # sort it out once they know the values of the coefficients return [Piecewise((a1, Eq(p, 0)), (a2, True)) for a1, a2 in zip(_ans(y1), _ans(y2))]
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))