Full factorization of polynomials over complexes with SymPy - python

I want to fully factorize a polynom, thus factorize it over complexes.
SymPy provide factor to do it, but I’m very surprised that factorization is done only over integer roots, e.g. :
>>> from sympy import *
>>> z = symbols('z')
>>> factor(z**2 - 1, z)
(z - 1)*(z + 1)
>>> factor(z**2 + 1, z)
z**2 + 1
or
>>> factor(2*z - 1, z)
2*z - 1
>>> factor(2*z - 1, z, extension=[Integer(1)/2])
2*(z - 1/2)
An answered question already exists : Factor to complex roots using sympy, and the solution given by asmeurer works :
>>> factor(z**2 + 1, z, extension=[I])
(z - I)*(z + I)
but you need to specify every divisor of non-integer roots, e.g. :
>>> factor(z**2 + 2, z, extension=[I])
z**2 + 2
>>> factor(z**2 + 2, z, extension=[I, sqrt(2)])
(z - sqrt(2)*I)*(z + sqrt(2)*I)
My question is : how to fully factorize a polynom (thus over complexes), without needing to give every divisor to extension ?
asmeurer gives a solution to do this :
>>> poly = z**2 + 2
>>> r = roots(poly, z)
>>> LC(poly, z)*Mul(*[(z - a)**r[a] for a in r])
/ ___ \ / ___ \
\z - \/ 2 *I/*\z + \/ 2 *I/
But it should exists a native way to do it, no ?
Someting like factor(poly, z, complex=True).
I looked for in the documentation of factor, but I did not find anything.
Futhermore, factor can take domain as optional argument that I believed allows to specified the set on which the factorization is made, but not
>>> factor(z**2 + 2, z, domain='Z')
2
z + 2
>>> factor(z**2 + 2, z, domain='R')
/ 2 \
2.0*\0.5*z + 1.0/
>>> factor(z**2 + 2, z, domain='C')
2
1.0*z + 2.0

The domain argument should work and in the case of Gaussian rationals you can also use gaussian=True which is equivalent to extension=I:
In [24]: factor(z**2 + 1, gaussian=True)
Out[24]: (z - ⅈ)⋅(z + ⅈ)
That doesn't work in your case though because the factorisation needs to be over QQ(I, sqrt(2)) rather than QQ(I). The reason that domains 'R' and 'C' don't work as expected is because they are inexact floating point domains rather than domains representing the real or complex numbers in the pure mathematical sense and factorisation is
The approaches above can be combined though with
In [28]: e = z**2 + 2
In [29]: factor(e, extension=roots(e))
Out[29]: (z - √2⋅ⅈ)⋅(z + √2⋅ⅈ)

Related

Sympy not finding a solution to equation, would numpy work?

I'm trying to build a system for simulating sliding done arbitrary slopes, but it requires that I find the intersection of half a circle and an arbitrary function. However, when I use sympy, it does find a solution despite one clearly existing.
Is there a numerical approach I could take to this in NumPy?
See code below, where x, y, and r and all constants:
z = symbols('z')
print(solve(z * 1, ((r**2 - (z - x)**2) ** 0.5) + y))
p1 = plot(z, show=False)
p2 = plot(((r**2 - (z - x)**2) ** 0.5) + y, show=False)
p1.append(p2[0])
p1.show()
This results in:
[]
Despite a solution clearly existing:
Note that in the case used for testing, x=0.4, y=0.4, r=1
You are passing two arguments to solve but it should be a single argument as an equation (Eq):
In [58]: eq = Eq(z * 1, ((r**2 - (z - x)**2) ** 0.5) + y)
In [59]: eq
Out[59]:
0.5
⎛ 2⎞
z = ⎝1 - (z - 0.4) ⎠ + 0.4
In [60]: solve(eq)
Out[60]: [1.10710678118655]
A second argument, if provided, should be the symbol to solve for or a list of symbols to solve for e.g.:
In [61]: solve(eq, z)
Out[61]: [1.10710678118655]

Changing coefficients modulo p in a SymPy polynomial

I took a cryptography course this semester in graduate school, and once of the topics we covered was NTRU. I am trying to code this in pure Python, purely as a hobby. When I attempt to find a polynomial's inverse modulo p (in this example p = 3), SymPy always returns negative coefficients, when I want strictly positive coefficients. Here is the code I have. I'll explain what I mean.
import sympy as sym
from sympy import GF
def make_poly(N,coeffs):
"""Create a polynomial in x."""
x = sym.Symbol('x')
coeffs = list(reversed(coeffs))
y = 0
for i in range(N):
y += (x**i)*coeffs[i]
y = sym.poly(y)
return y
N = 7
p = 3
q = 41
f = [1,0,-1,1,1,0,-1]
f_poly = make_poly(N,f)
x = sym.Symbol('x')
Fp = sym.polys.polytools.invert(f_poly,x**N-1,domain=GF(p))
Fq = sym.polys.polytools.invert(f_poly,x**N-1,domain=GF(q))
print('\nf =',f_poly)
print('\nFp =',Fp)
print('\nFq =',Fq)
In this code, f_poly is a polynomial with degree at most 6 (its degree is at most N-1), whose coefficients come from the list f (the first entry in f is the coefficient on the highest power of x, continuing in descending order).
Now, I want to find the inverse polynomial of f_poly in the convolution polynomial ring Rp = (Z/pZ)[x]/(x^N - 1)(Z/pZ)[x] (similarly for q). The output of the print statements at the bottom are:
f = Poly(x**6 - x**4 + x**3 + x**2 - 1, x, domain='ZZ')
Fp = Poly(x**6 - x**5 + x**3 + x**2 + x + 1, x, modulus=3)
Fq = Poly(8*x**6 - 15*x**5 - 10*x**4 - 20*x**3 - x**2 + 2*x - 4, x, modulus=41)
These polynomials are correct in modulus, but I would like to have positive coefficients everywhere, as later on in the algorithm there is some centerlifting involved, so I need to have positive coefficients. The results should be
Fp = x^6 + 2x^5 + x^3 + x^2 + x + 1
Fq = 8x^6 + 26x^5 + 31x^4 + 21x^3 + 40x^2 + 2x + 37
The answers I'm getting are correct in modulus, but I think that SymPy's invert is changing some of the coefficients to negative variants, instead of staying inside the mod.
Is there any way I can update the coefficients of this polynomial to have only positive coefficients in modulus, or is this just an artifact of SymPy's function? I want to keep the SymPy Poly format so I can use some of its embedded functions later on down the line. Any insight would be much appreciated!
This seems to be down to how the finite field object implemented in GF "wraps" integers around the given modulus. The default behavior is symmetric, which means that any integer x for which x % modulo <= modulo//2 maps to x % modulo, and otherwise maps to (x % modulo) - modulo. So GF(10)(5) == 5, whereas GF(10)(6) == -4. You can make GF always map to positive numbers instead by passing the symmetric=False argument:
import sympy as sym
from sympy import GF
def make_poly(N, coeffs):
"""Create a polynomial in x."""
x = sym.Symbol('x')
coeffs = list(reversed(coeffs))
y = 0
for i in range(N):
y += (x**i)*coeffs[i]
y = sym.poly(y)
return y
N = 7
p = 3
q = 41
f = [1,0,-1,1,1,0,-1]
f_poly = make_poly(N,f)
x = sym.Symbol('x')
Fp = sym.polys.polytools.invert(f_poly,x**N-1,domain=GF(p, symmetric=False))
Fq = sym.polys.polytools.invert(f_poly,x**N-1,domain=GF(q, symmetric=False))
print('\nf =',f_poly)
print('\nFp =',Fp)
print('\nFq =',Fq)
Now you'll get the polynomials you wanted. The output from the print(...) statements at the end of the example should look like:
f = Poly(x**6 - x**4 + x**3 + x**2 - 1, x, domain='ZZ')
Fp = Poly(x**6 + 2*x**5 + x**3 + x**2 + x + 1, x, modulus=3)
Fq = Poly(8*x**6 + 26*x**5 + 31*x**4 + 21*x**3 + 40*x**2 + 2*x + 37, x, modulus=41)
Mostly as a note for my own reference, here's how you would get Fp using Mathematica:
Fp = PolynomialMod[Algebra`PolynomialPowerMod`PolynomialPowerMod[x^6 - x^4 + x^3 + x^2 - 1, -1, x, x^7 - 1], 3]
output:
1 + x + x^2 + x^3 + 2 x^5 + x^6

Pythonic Way to Solve this Matrix

I've been thinking on this problem, but I can't seem to wrap my head around it.
I want to solve a matrix with three equations with unknowns x, y, z so they all equal the same number.
Lets say my equations are:
x + 3 = A
y(2y - 2) = 2A
z(4z - 1) = A
So I can construct a matrix looking like:
[(X + 3) , 0 , 0] [0] [A]
[ 0 ,(2y - 2), 0] [y] = [2A]
[ 0 , , 0, (4z -1)] [z] [A]
I know numpy has a linear algebra but that is only when the answer (A) is already known.
My question is, would I have to construct a loop to brute force the answer of (A) or is there a more pythonic way of answering these series of equations?
Linear algebra can only solve for multiples of your variables, not powers (that is why it is called linear, ie the equation for a straight line, Ax + By + Cz = 0).
For this set of equations you can use the quadratic formula to solve in terms of a:
x + 3 = a => x = a - 3
y * (y - 1) = a => y**2 - y - a = 0
y = (1 +/- (1 + 4*a) ** 0.5) / 2
= 0.5 +/- (0.25 + a) ** 0.5
(a >= -0.25 for real roots)
z * (4*z - 1) = a => 4 * z**2 - z - a = 0
z = (1 +/- (1 + 16*a) ** 0.5) / 8
= 0.125 +/- (0.015625 + 0.25*a) ** 0.5
(a >= -0.0625 for real roots)
then
def solve(a):
assert a >= -0.625, "No real solution"
x = a - 3
yoffs = (0.25 * a) ** 0.5
ylo = 0.5 - yoffs
yhi = 0.5 + yoffs
zoffs = (0.015625 + 0.25 * a) ** 0.5
zlo = 0.125 - zoffs
zhi = 0.125 + zoffs
return [
(x, ylo, zlo),
(x, ylo, zhi),
(x, yhi, zlo),
(x, yhi, zhi)
]
You do not have a system of 3 equations with 3 unknowns. You have a system of 3 equations with 4 unknowns: x, y, z and A.
That means your answer will be parameterized on A, because you do not have enough equations to solve for all unknowns.
Solving a general system of polynomial equations can be done by the so-called Groebner basis approach, which is what sympy uses. Here is a snippet on how to use the library to solve this or similar problems:
from sympy.solvers.polysys import solve_poly_system
from sympy.abc import x, y, z, A
f1 = x + 3 - A
f2 = y * (2 * y - 2) - 2 * A
f3 = z * (4 * z - 1) - A
solve_poly_system([f1, f2, f3], x, y, z)
# Outputs:
# [(A - 3, -sqrt(4*A + 1)/2 + 1/2, -sqrt(16*A + 1)/8 + 1/8),
# (A - 3, -sqrt(4*A + 1)/2 + 1/2, sqrt(16*A + 1)/8 + 1/8),
# (A - 3, sqrt(4*A + 1)/2 + 1/2, -sqrt(16*A + 1)/8 + 1/8),
# (A - 3, sqrt(4*A + 1)/2 + 1/2, sqrt(16*A + 1)/8 + 1/8)]
As you can see, the result requires to fix the value of A to be fully determined.

Factoring polys in sympy

I'm doing a very simple probability calculations of getting subset of X, Y, Z from set of A-Z (with corresponding probabilities x, y, z).
And because of very heavy formulas, in order to handle them, I'm trying to simplify (or collect or factor - I dont know the exact definition) these polynomial expressions using sympy.
So.. having this (a very simple probability calculation expression of getting subset of X,Y,Z from set of A-Z with corresponding probabilities x, y, z)
import sympy as sp
x, y, z = sp.symbols('x y z')
expression = (
x * (1 - x) * y * (1 - x - y) * z +
x * (1 - x) * z * (1 - x - z) * y +
y * (1 - y) * x * (1 - y - x) * z +
y * (1 - y) * z * (1 - y - z) * x +
z * (1 - z) * y * (1 - z - y) * x +
z * (1 - z) * x * (1 - z - x) * y
)
I want to get something like this
x * y * z * (6 * (1 - x - y - z) + (x + y) ** 2 + (y + z) ** 2 + (x + z) ** 2)
a poly, rewritten in way to have as few operations (+, -, *, **, ...) as possible
I tried factor(), collect(), simplify(). But result differs from my expectations. Mostly I get
2*x*y*z*(x**2 + x*y + x*z - 3*x + y**2 + y*z - 3*y + z**2 - 3*z + 3)
I know that sympy can combine polynomials into simple forms:
sp.factor(x**2 + 2*x*y + y**2) # gives (x + y)**2
But how to make sympy to combine polynomials from expressions above?
If this is impossible task in sympy, may be there are any other options?
Putting together some of the methods happens to give a nice answer this time. It would be interesting to see if this strategy works more often than not on the equations you generate or if, as the name implies, this is just a lucky result this time.
def iflfactor(eq):
"""Return the "I'm feeling lucky" factored form of eq."""
e = Mul(*[horner(e) if e.is_Add else e for e in
Mul.make_args(factor_terms(expand(eq)))])
r, e = cse(e)
s = [ri[0] for ri in r]
e = Mul(*[collect(ei.expand(), s) if ei.is_Add else ei for ei in
Mul.make_args(e[0])]).subs(r)
return e
>>> iflfactor(eq) # using your equation as eq
2*x*y*z*(x**2 + x*y + y**2 + (z - 3)*(x + y + z) + 3)
>>> _.count_ops()
15
BTW, a difference between factor_terms and gcd_terms is that factor_terms will work harder to pull out common terms while retaining the original structure of the expression, very much like you would do by hand (i.e. looking for common terms in Adds that can be pulled out).
>>> factor_terms(x/(z+z*y)+x/z)
x*(1 + 1/(y + 1))/z
>>> gcd_terms(x/(z+z*y)+x/z)
x*(y*z + 2*z)/(z*(y*z + z))
For what it's worth,
Chris
As far as I know, there is no function that does exactly that. I believe it is actually a very hard problem. See Reduce the number of operations on a simple expression for some discussion on it.
There are however, quite a few simplification functions in SymPy that you can try. One that you haven't mentioned that gives a different result is gcd_terms, which factorizes out a symbolic gcd without doing an expansions. It gives
>>> gcd_terms(expression)
x*y*z*((-x + 1)*(-x - y + 1) + (-x + 1)*(-x - z + 1) + (-y + 1)*(-x - y + 1) + (-y + 1)*(-y - z + 1) + (-z + 1)*(-x - z + 1) + (-z + 1)*(-y - z + 1))
Another useful function is .count_ops, which counts the number of operations in an expression. For example
>>> expression.count_ops()
47
>>> factor(expression).count_ops()
22
>>> e = x * y * z * (6 * (1 - x - y - z) + (x + y) ** 2 + (y + z) ** 2 + (x + z) ** 2)
>>> e.count_ops()
18
(note that e.count_ops() is not the same as you counted yourself, because SymPy automatically distributes the 6*(1 - x - y - z) to 6 - 6*x - 6*y - 6*z).
Other useful functions:
cse: Performs a common subexpression elimination on the expression. Sometimes you can simplify the individual parts and then put it back together. This also helps in general to avoid duplicate computations.
horner: Applies the Horner scheme to a polynomial. This minimizes the number of operations if the polynomial is in one variable.
factor_terms: Similar to gcd_terms. I'm actually not entirely clear what the difference is.
Note that by default, simplify will try several simplifications, and return the one that is minimized by count_ops.
I have had a similar problem, and ended up implementing my own solution before I stumbled across this one. Mine seems to do a much better job reducing the number of operations. However, mine also does a brute-force style set of collections over all combinations of variables. Thus, it's runtime grows super-exponentially in the number of variables. OTOH, I've managed to run it on equations with 7 variables in a not-unreasonable (but far from real-time) amount of time.
It is possible that there are some ways to prune some of the search branches here, but I haven't bothered with it. Further optimizations are welcome.
def collect_best(expr, measure=sympy.count_ops):
# This method performs sympy.collect over all permutations of the free variables, and returns the best collection
best = expr
best_score = measure(expr)
perms = itertools.permutations(expr.free_symbols)
permlen = np.math.factorial(len(expr.free_symbols))
print(permlen)
for i, perm in enumerate(perms):
if (permlen > 1000) and not (i%int(permlen/100)):
print(i)
collected = sympy.collect(expr, perm)
if measure(collected) < best_score:
best_score = measure(collected)
best = collected
return best
def product(args):
arg = next(args)
try:
return arg*product(args)
except:
return arg
def rcollect_best(expr, measure=sympy.count_ops):
# This method performs collect_best recursively on the collected terms
best = collect_best(expr, measure)
best_score = measure(best)
if expr == best:
return best
if isinstance(best, sympy.Mul):
return product(map(rcollect_best, best.args))
if isinstance(best, sympy.Add):
return sum(map(rcollect_best, best.args))
To illustrate the performance, this paper(paywalled, sorry) has 7 formulae that are 5th degree polynomials in 7 variables with up to 29 terms and 158 operations in the expanded forms. After applying both rcollect_best and #smichr's iflfactor, the number of operations in the 7 formulae are:
[6, 15, 100, 68, 39, 13, 2]
and
[32, 37, 113, 73, 40, 15, 2]
respectively. iflfactor has 433% more operations than rcollect_best for one of the formulae. Also, the number of operations in the expanded formulae are:
[39, 49, 158, 136, 79, 27, 2]

Solving polynomial equations in Python

Up to now I have always Mathematica for solving analytical equations. Now however I need to solve a few hundred equations of this type (characteristic polynomials)
a_20*x^20+a_19*x^19+...+a_1*x+a_0=0 (constant floats a_0,...a_20)
at once which yields awfully long calculation times in Mathematica.
Is there like a ready to use command in numpy or any other package to solve an equation of this type? (up to now I have used Python only for simulations so I don't know much about analytical tools and I couldn't find anything useful in the numpy tutorials).
You use numpy (apparently), but I've never tried it myself though: http://docs.scipy.org/doc/numpy/reference/generated/numpy.roots.html#numpy.roots.
Numpy also provides a polynomial class... numpy.poly1d.
This finds the roots numerically -- if you want the analytical roots, I don't think numpy can do that for you.
Here is an example from simpy docs:
>>> from sympy import *
>>> x = symbols('x')
>>> from sympy import roots, solve_poly_system
>>> solve(x**3 + 2*x + 3, x)
____ ____
1 \/ 11 *I 1 \/ 11 *I
[-1, - - --------, - + --------]
2 2 2 2
>>> p = Symbol('p')
>>> q = Symbol('q')
>>> sorted(solve(x**2 + p*x + q, x))
__________ __________
/ 2 / 2
p \/ p - 4*q p \/ p - 4*q
[- - + -------------, - - - -------------]
2 2 2 2
>>> solve_poly_system([y - x, x - 5], x, y)
[(5, 5)]
>>> solve_poly_system([y**2 - x**3 + 1, y*x], x, y)
___ ___
1 \/ 3 *I 1 \/ 3 *I
[(0, I), (0, -I), (1, 0), (- - + -------, 0), (- - - -------, 0)]
2 2 2 2
(a link to the docs with this example)
You may want to look at SAGE which is a complete python distribution designed for mathematical processing. Beyond that, I have used Sympy for somewhat similar matters, as Marcin highlighted.
import decimal as dd
degree = int(input('What is the highest co-efficient of x? '))
coeffs = [0]* (degree + 1)
coeffs1 = {}
dd.getcontext().prec = 10
for ii in range(degree,-1,-1):
if ii != 0:
res=dd.Decimal(input('what is the coefficient of x^ %s ? '%ii))
coeffs[ii] = res
coeffs1.setdefault('x^ %s ' % ii, res)
else:
res=dd.Decimal(input('what is the constant term ? '))
coeffs[ii] = res
coeffs1.setdefault('CT', res)
coeffs = coeffs[::-1]
def contextmg(start,stop,step):
r = start
while r < stop:
yield r
r += step
def ell(a,b,c):
vals=contextmg(a,b,c)
context = ['%.10f' % it for it in vals]
return context
labels = [0]*degree
for ll in range(degree):
labels[ll] = 'x%s'%(ll+1)
roots = {}
context = ell(-20,20,0.0001)
for x in context:
for xx in range(degree):
if xx == 0:
calculatoR = (coeffs[xx]* dd.Decimal(x)) + coeffs[xx+1]
else:
calculatoR = calculatoR * dd.Decimal(x) + coeffs[xx+1]
func =round(float(calculatoR),2)
xp = round(float(x),3)
if func==0 and roots=={} :
roots[labels[0]] = xp
labels = labels[1:]
p = xp
elif func == 0 and xp >(0.25 + p):
roots[labels[0]] = xp
labels = labels[1:]
p = xp
print(roots)

Categories

Resources