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 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 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 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 _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 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.simplify.simplify 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 3435 assert (2 * n + 2 * m).factor() == 2 * (n + m) # issue 3602 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
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.simplify.simplify 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 3435 assert (2*n + 2*m).factor() == 2*(n + m) # issue 3602 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
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 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 _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') rhs = -c / b + (a / d) * LambertW( d / (a * b) * exp(c * d / a / b) * exp(-f / a)) # if W's arg is between -1/e and 0 there is a -1 branch solution, too. # Check here to see if exp(W(s)) appears and return s/W(s) instead? solns = solve(X1 - u, x) for i, tmp in enumerate(solns): solns[i] = tmp.subs(u, rhs) return solns
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') rhs = -c/b + (a/d)*LambertW(d/(a*b)*exp(c*d/a/b)*exp(-f/a)) # if W's arg is between -1/e and 0 there is a -1 branch solution, too. # Check here to see if exp(W(s)) appears and return s/W(s) instead? solns = solve(X1 - u, x) for i, tmp in enumerate(solns): solns[i] = tmp.subs(u, rhs) return solns
def test_factor_nc(): x, y = symbols('x,y') n, m, o = symbols('n,m,o', commutative=False) # mul and multinomial expansion is needed from sympy.simplify.simplify 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
def factor_nc(expr): """Return the factored form of ``expr`` while handling non-commutative expressions. **examples** >>> from sympy.core.exprtools import factor_nc >>> from sympy import Symbol >>> from sympy.abc import x >>> A = Symbol('A', commutative=False) >>> B = Symbol('B', commutative=False) >>> factor_nc((x**2 + 2*A*x + A**2).expand()) (x + A)**2 >>> factor_nc(((x + A)*(x + B)).expand()) (x + A)*(x + B) """ from sympy.simplify.simplify import _mexpand from sympy.polys import gcd, factor expr = sympify(expr) if not isinstance(expr, Expr) or not expr.args: return expr if not expr.is_Add: return expr.func(*[factor_nc(a) for a in expr.args]) expr, rep, nc_symbols = _mask_nc(expr) if rep: return factor(expr).subs(rep) else: args = [a.args_cnc() for a in Add.make_args(expr)] c = g = l = r = S.One hit = False # find any commutative gcd term for i, a in enumerate(args): if i == 0: c = Mul._from_args(a[0]) elif a[0]: c = gcd(c, Mul._from_args(a[0])) else: c = S.One if c is not S.One: hit = True c, g = c.as_coeff_Mul() if g is not S.One: for i, (cc, _) in enumerate(args): cc = list(Mul.make_args(Mul._from_args(list(cc))/g)) args[i][0] = cc else: for i, (cc, _) in enumerate(args): cc[0] = cc[0]/c args[i][0] = cc # find any noncommutative common prefix for i, a in enumerate(args): if i == 0: n = a[1][:] else: n = common_prefix(n, a[1]) if not n: # is there a power that can be extracted? if not args[0][1]: break b, e = args[0][1][0].as_base_exp() ok = False if e.is_Integer: for t in args: if not t[1]: break bt, et = t[1][0].as_base_exp() if et.is_Integer and bt == b: e = min(e, et) else: break else: ok = hit = True l = b**e il = b**-e for i, a in enumerate(args): args[i][1][0] = il*args[i][1][0] break if not ok: break else: hit = True lenn = len(n) l = Mul(*n) for i, a in enumerate(args): args[i][1] = args[i][1][lenn:] # find any noncommutative common suffix for i, a in enumerate(args): if i == 0: n = a[1][:] else: n = common_suffix(n, a[1]) if not n: # is there a power that can be extracted? if not args[0][1]: break b, e = args[0][1][-1].as_base_exp() ok = False if e.is_Integer: for t in args: if not t[1]: break bt, et = t[1][-1].as_base_exp() if et.is_Integer and bt == b: e = min(e, et) else: break else: ok = hit = True r = b**e il = b**-e for i, a in enumerate(args): args[i][1][-1] = args[i][1][-1]*il break if not ok: break else: hit = True lenn = len(n) r = Mul(*n) for i, a in enumerate(args): args[i][1] = a[1][:len(a[1]) - lenn] if hit: mid = Add(*[Mul(*cc)*Mul(*nc) for cc, nc in args]) else: mid = expr # sort the symbols so the Dummys would appear in the same # order as the original symbols, otherwise you may introduce # a factor of -1, e.g. A**2 - B**2) -- {A:y, B:x} --> y**2 - x**2 # and the former factors into two terms, (A - B)*(A + B) while the # latter factors into 3 terms, (-1)*(x - y)*(x + y) rep1 = [(n, Dummy()) for n in sorted(nc_symbols, key=default_sort_key)] unrep1 = [(v, k) for k, v in rep1] unrep1.reverse() new_mid, r2, _ = _mask_nc(mid.subs(rep1)) new_mid = factor(new_mid) new_mid = new_mid.subs(r2).subs(unrep1) if new_mid.is_Pow: return _keep_coeff(c, g*l*new_mid*r) if new_mid.is_Mul: # XXX TODO there should be a way to inspect what order the terms # must be in and just select the plausible ordering without # checking permutations cfac = [] ncfac = [] for f in new_mid.args: if f.is_commutative: cfac.append(f) else: b, e = f.as_base_exp() assert e.is_Integer ncfac.extend([b]*e) pre_mid = g*Mul(*cfac)*l target = _mexpand(expr/c) for s in variations(ncfac, len(ncfac)): ok = pre_mid*Mul(*s)*r if _mexpand(ok) == target: return _keep_coeff(c, ok) # mid was an Add that didn't factor successfully return _keep_coeff(c, g*l*mid*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.simplify.simplify import _split_gcd, _mexpand 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 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 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 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 _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.simplify.simplify import _split_gcd, _mexpand 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