Spaces:
Running
Running
""" | |
This module contains functions for the computation | |
of Euclidean, (generalized) Sturmian, (modified) subresultant | |
polynomial remainder sequences (prs's) of two polynomials; | |
included are also three functions for the computation of the | |
resultant of two polynomials. | |
Except for the function res_z(), which computes the resultant | |
of two polynomials, the pseudo-remainder function prem() | |
of sympy is _not_ used by any of the functions in the module. | |
Instead of prem() we use the function | |
rem_z(). | |
Included is also the function quo_z(). | |
An explanation of why we avoid prem() can be found in the | |
references stated in the docstring of rem_z(). | |
1. Theoretical background: | |
========================== | |
Consider the polynomials f, g in Z[x] of degrees deg(f) = n and | |
deg(g) = m with n >= m. | |
Definition 1: | |
============= | |
The sign sequence of a polynomial remainder sequence (prs) is the | |
sequence of signs of the leading coefficients of its polynomials. | |
Sign sequences can be computed with the function: | |
sign_seq(poly_seq, x) | |
Definition 2: | |
============= | |
A polynomial remainder sequence (prs) is called complete if the | |
degree difference between any two consecutive polynomials is 1; | |
otherwise, it called incomplete. | |
It is understood that f, g belong to the sequences mentioned in | |
the two definitions above. | |
1A. Euclidean and subresultant prs's: | |
===================================== | |
The subresultant prs of f, g is a sequence of polynomials in Z[x] | |
analogous to the Euclidean prs, the sequence obtained by applying | |
on f, g Euclid's algorithm for polynomial greatest common divisors | |
(gcd) in Q[x]. | |
The subresultant prs differs from the Euclidean prs in that the | |
coefficients of each polynomial in the former sequence are determinants | |
--- also referred to as subresultants --- of appropriately selected | |
sub-matrices of sylvester1(f, g, x), Sylvester's matrix of 1840 of | |
dimensions (n + m) * (n + m). | |
Recall that the determinant of sylvester1(f, g, x) itself is | |
called the resultant of f, g and serves as a criterion of whether | |
the two polynomials have common roots or not. | |
In SymPy the resultant is computed with the function | |
resultant(f, g, x). This function does _not_ evaluate the | |
determinant of sylvester(f, g, x, 1); instead, it returns | |
the last member of the subresultant prs of f, g, multiplied | |
(if needed) by an appropriate power of -1; see the caveat below. | |
In this module we use three functions to compute the | |
resultant of f, g: | |
a) res(f, g, x) computes the resultant by evaluating | |
the determinant of sylvester(f, g, x, 1); | |
b) res_q(f, g, x) computes the resultant recursively, by | |
performing polynomial divisions in Q[x] with the function rem(); | |
c) res_z(f, g, x) computes the resultant recursively, by | |
performing polynomial divisions in Z[x] with the function prem(). | |
Caveat: If Df = degree(f, x) and Dg = degree(g, x), then: | |
resultant(f, g, x) = (-1)**(Df*Dg) * resultant(g, f, x). | |
For complete prs's the sign sequence of the Euclidean prs of f, g | |
is identical to the sign sequence of the subresultant prs of f, g | |
and the coefficients of one sequence are easily computed from the | |
coefficients of the other. | |
For incomplete prs's the polynomials in the subresultant prs, generally | |
differ in sign from those of the Euclidean prs, and --- unlike the | |
case of complete prs's --- it is not at all obvious how to compute | |
the coefficients of one sequence from the coefficients of the other. | |
1B. Sturmian and modified subresultant prs's: | |
============================================= | |
For the same polynomials f, g in Z[x] mentioned above, their ``modified'' | |
subresultant prs is a sequence of polynomials similar to the Sturmian | |
prs, the sequence obtained by applying in Q[x] Sturm's algorithm on f, g. | |
The two sequences differ in that the coefficients of each polynomial | |
in the modified subresultant prs are the determinants --- also referred | |
to as modified subresultants --- of appropriately selected sub-matrices | |
of sylvester2(f, g, x), Sylvester's matrix of 1853 of dimensions 2n x 2n. | |
The determinant of sylvester2 itself is called the modified resultant | |
of f, g and it also can serve as a criterion of whether the two | |
polynomials have common roots or not. | |
For complete prs's the sign sequence of the Sturmian prs of f, g is | |
identical to the sign sequence of the modified subresultant prs of | |
f, g and the coefficients of one sequence are easily computed from | |
the coefficients of the other. | |
For incomplete prs's the polynomials in the modified subresultant prs, | |
generally differ in sign from those of the Sturmian prs, and --- unlike | |
the case of complete prs's --- it is not at all obvious how to compute | |
the coefficients of one sequence from the coefficients of the other. | |
As Sylvester pointed out, the coefficients of the polynomial remainders | |
obtained as (modified) subresultants are the smallest possible without | |
introducing rationals and without computing (integer) greatest common | |
divisors. | |
1C. On terminology: | |
=================== | |
Whence the terminology? Well generalized Sturmian prs's are | |
``modifications'' of Euclidean prs's; the hint came from the title | |
of the Pell-Gordon paper of 1917. | |
In the literature one also encounters the name ``non signed'' and | |
``signed'' prs for Euclidean and Sturmian prs respectively. | |
Likewise ``non signed'' and ``signed'' subresultant prs for | |
subresultant and modified subresultant prs respectively. | |
2. Functions in the module: | |
=========================== | |
No function utilizes SymPy's function prem(). | |
2A. Matrices: | |
============= | |
The functions sylvester(f, g, x, method=1) and | |
sylvester(f, g, x, method=2) compute either Sylvester matrix. | |
They can be used to compute (modified) subresultant prs's by | |
direct determinant evaluation. | |
The function bezout(f, g, x, method='prs') provides a matrix of | |
smaller dimensions than either Sylvester matrix. It is the function | |
of choice for computing (modified) subresultant prs's by direct | |
determinant evaluation. | |
sylvester(f, g, x, method=1) | |
sylvester(f, g, x, method=2) | |
bezout(f, g, x, method='prs') | |
The following identity holds: | |
bezout(f, g, x, method='prs') = | |
backward_eye(deg(f))*bezout(f, g, x, method='bz')*backward_eye(deg(f)) | |
2B. Subresultant and modified subresultant prs's by | |
=================================================== | |
determinant evaluations: | |
======================= | |
We use the Sylvester matrices of 1840 and 1853 to | |
compute, respectively, subresultant and modified | |
subresultant polynomial remainder sequences. However, | |
for large matrices this approach takes a lot of time. | |
Instead of utilizing the Sylvester matrices, we can | |
employ the Bezout matrix which is of smaller dimensions. | |
subresultants_sylv(f, g, x) | |
modified_subresultants_sylv(f, g, x) | |
subresultants_bezout(f, g, x) | |
modified_subresultants_bezout(f, g, x) | |
2C. Subresultant prs's by ONE determinant evaluation: | |
===================================================== | |
All three functions in this section evaluate one determinant | |
per remainder polynomial; this is the determinant of an | |
appropriately selected sub-matrix of sylvester1(f, g, x), | |
Sylvester's matrix of 1840. | |
To compute the remainder polynomials the function | |
subresultants_rem(f, g, x) employs rem(f, g, x). | |
By contrast, the other two functions implement Van Vleck's ideas | |
of 1900 and compute the remainder polynomials by trinagularizing | |
sylvester2(f, g, x), Sylvester's matrix of 1853. | |
subresultants_rem(f, g, x) | |
subresultants_vv(f, g, x) | |
subresultants_vv_2(f, g, x). | |
2E. Euclidean, Sturmian prs's in Q[x]: | |
====================================== | |
euclid_q(f, g, x) | |
sturm_q(f, g, x) | |
2F. Euclidean, Sturmian and (modified) subresultant prs's P-G: | |
============================================================== | |
All functions in this section are based on the Pell-Gordon (P-G) | |
theorem of 1917. | |
Computations are done in Q[x], employing the function rem(f, g, x) | |
for the computation of the remainder polynomials. | |
euclid_pg(f, g, x) | |
sturm pg(f, g, x) | |
subresultants_pg(f, g, x) | |
modified_subresultants_pg(f, g, x) | |
2G. Euclidean, Sturmian and (modified) subresultant prs's A-M-V: | |
================================================================ | |
All functions in this section are based on the Akritas-Malaschonok- | |
Vigklas (A-M-V) theorem of 2015. | |
Computations are done in Z[x], employing the function rem_z(f, g, x) | |
for the computation of the remainder polynomials. | |
euclid_amv(f, g, x) | |
sturm_amv(f, g, x) | |
subresultants_amv(f, g, x) | |
modified_subresultants_amv(f, g, x) | |
2Ga. Exception: | |
=============== | |
subresultants_amv_q(f, g, x) | |
This function employs rem(f, g, x) for the computation of | |
the remainder polynomials, despite the fact that it implements | |
the A-M-V Theorem. | |
It is included in our module in order to show that theorems P-G | |
and A-M-V can be implemented utilizing either the function | |
rem(f, g, x) or the function rem_z(f, g, x). | |
For clearly historical reasons --- since the Collins-Brown-Traub | |
coefficients-reduction factor beta_i was not available in 1917 --- | |
we have implemented the Pell-Gordon theorem with the function | |
rem(f, g, x) and the A-M-V Theorem with the function rem_z(f, g, x). | |
2H. Resultants: | |
=============== | |
res(f, g, x) | |
res_q(f, g, x) | |
res_z(f, g, x) | |
""" | |
from sympy.concrete.summations import summation | |
from sympy.core.function import expand | |
from sympy.core.numbers import nan | |
from sympy.core.singleton import S | |
from sympy.core.symbol import Dummy as var | |
from sympy.functions.elementary.complexes import Abs, sign | |
from sympy.functions.elementary.integers import floor | |
from sympy.matrices.dense import eye, Matrix, zeros | |
from sympy.printing.pretty.pretty import pretty_print as pprint | |
from sympy.simplify.simplify import simplify | |
from sympy.polys.domains import QQ | |
from sympy.polys.polytools import degree, LC, Poly, pquo, quo, prem, rem | |
from sympy.polys.polyerrors import PolynomialError | |
def sylvester(f, g, x, method = 1): | |
''' | |
The input polynomials f, g are in Z[x] or in Q[x]. Let m = degree(f, x), | |
n = degree(g, x) and mx = max(m, n). | |
a. If method = 1 (default), computes sylvester1, Sylvester's matrix of 1840 | |
of dimension (m + n) x (m + n). The determinants of properly chosen | |
submatrices of this matrix (a.k.a. subresultants) can be | |
used to compute the coefficients of the Euclidean PRS of f, g. | |
b. If method = 2, computes sylvester2, Sylvester's matrix of 1853 | |
of dimension (2*mx) x (2*mx). The determinants of properly chosen | |
submatrices of this matrix (a.k.a. ``modified'' subresultants) can be | |
used to compute the coefficients of the Sturmian PRS of f, g. | |
Applications of these Matrices can be found in the references below. | |
Especially, for applications of sylvester2, see the first reference!! | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem | |
by Van Vleck Regarding Sturm Sequences. Serdica Journal of Computing, | |
Vol. 7, No 4, 101-134, 2013. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
''' | |
# obtain degrees of polys | |
m, n = degree( Poly(f, x), x), degree( Poly(g, x), x) | |
# Special cases: | |
# A:: case m = n < 0 (i.e. both polys are 0) | |
if m == n and n < 0: | |
return Matrix([]) | |
# B:: case m = n = 0 (i.e. both polys are constants) | |
if m == n and n == 0: | |
return Matrix([]) | |
# C:: m == 0 and n < 0 or m < 0 and n == 0 | |
# (i.e. one poly is constant and the other is 0) | |
if m == 0 and n < 0: | |
return Matrix([]) | |
elif m < 0 and n == 0: | |
return Matrix([]) | |
# D:: m >= 1 and n < 0 or m < 0 and n >=1 | |
# (i.e. one poly is of degree >=1 and the other is 0) | |
if m >= 1 and n < 0: | |
return Matrix([0]) | |
elif m < 0 and n >= 1: | |
return Matrix([0]) | |
fp = Poly(f, x).all_coeffs() | |
gp = Poly(g, x).all_coeffs() | |
# Sylvester's matrix of 1840 (default; a.k.a. sylvester1) | |
if method <= 1: | |
M = zeros(m + n) | |
k = 0 | |
for i in range(n): | |
j = k | |
for coeff in fp: | |
M[i, j] = coeff | |
j = j + 1 | |
k = k + 1 | |
k = 0 | |
for i in range(n, m + n): | |
j = k | |
for coeff in gp: | |
M[i, j] = coeff | |
j = j + 1 | |
k = k + 1 | |
return M | |
# Sylvester's matrix of 1853 (a.k.a sylvester2) | |
if method >= 2: | |
if len(fp) < len(gp): | |
h = [] | |
for i in range(len(gp) - len(fp)): | |
h.append(0) | |
fp[ : 0] = h | |
else: | |
h = [] | |
for i in range(len(fp) - len(gp)): | |
h.append(0) | |
gp[ : 0] = h | |
mx = max(m, n) | |
dim = 2*mx | |
M = zeros( dim ) | |
k = 0 | |
for i in range( mx ): | |
j = k | |
for coeff in fp: | |
M[2*i, j] = coeff | |
j = j + 1 | |
j = k | |
for coeff in gp: | |
M[2*i + 1, j] = coeff | |
j = j + 1 | |
k = k + 1 | |
return M | |
def process_matrix_output(poly_seq, x): | |
""" | |
poly_seq is a polynomial remainder sequence computed either by | |
(modified_)subresultants_bezout or by (modified_)subresultants_sylv. | |
This function removes from poly_seq all zero polynomials as well | |
as all those whose degree is equal to the degree of a preceding | |
polynomial in poly_seq, as we scan it from left to right. | |
""" | |
L = poly_seq[:] # get a copy of the input sequence | |
d = degree(L[1], x) | |
i = 2 | |
while i < len(L): | |
d_i = degree(L[i], x) | |
if d_i < 0: # zero poly | |
L.remove(L[i]) | |
i = i - 1 | |
if d == d_i: # poly degree equals degree of previous poly | |
L.remove(L[i]) | |
i = i - 1 | |
if d_i >= 0: | |
d = d_i | |
i = i + 1 | |
return L | |
def subresultants_sylv(f, g, x): | |
""" | |
The input polynomials f, g are in Z[x] or in Q[x]. It is assumed | |
that deg(f) >= deg(g). | |
Computes the subresultant polynomial remainder sequence (prs) | |
of f, g by evaluating determinants of appropriately selected | |
submatrices of sylvester(f, g, x, 1). The dimensions of the | |
latter are (deg(f) + deg(g)) x (deg(f) + deg(g)). | |
Each coefficient is computed by evaluating the determinant of the | |
corresponding submatrix of sylvester(f, g, x, 1). | |
If the subresultant prs is complete, then the output coincides | |
with the Euclidean sequence of the polynomials f, g. | |
References: | |
=========== | |
1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants | |
and Their Applications. Appl. Algebra in Engin., Communic. and Comp., | |
Vol. 15, 233-266, 2004. | |
""" | |
# make sure neither f nor g is 0 | |
if f == 0 or g == 0: | |
return [f, g] | |
n = degF = degree(f, x) | |
m = degG = degree(g, x) | |
# make sure proper degrees | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, degF, degG, f, g = m, n, degG, degF, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
SR_L = [f, g] # subresultant list | |
# form matrix sylvester(f, g, x, 1) | |
S = sylvester(f, g, x, 1) | |
# pick appropriate submatrices of S | |
# and form subresultant polys | |
j = m - 1 | |
while j > 0: | |
Sp = S[:, :] # copy of S | |
# delete last j rows of coeffs of g | |
for ind in range(m + n - j, m + n): | |
Sp.row_del(m + n - j) | |
# delete last j rows of coeffs of f | |
for ind in range(m - j, m): | |
Sp.row_del(m - j) | |
# evaluate determinants and form coefficients list | |
coeff_L, k, l = [], Sp.rows, 0 | |
while l <= j: | |
coeff_L.append(Sp[:, 0:k].det()) | |
Sp.col_swap(k - 1, k + l) | |
l += 1 | |
# form poly and append to SP_L | |
SR_L.append(Poly(coeff_L, x).as_expr()) | |
j -= 1 | |
# j = 0 | |
SR_L.append(S.det()) | |
return process_matrix_output(SR_L, x) | |
def modified_subresultants_sylv(f, g, x): | |
""" | |
The input polynomials f, g are in Z[x] or in Q[x]. It is assumed | |
that deg(f) >= deg(g). | |
Computes the modified subresultant polynomial remainder sequence (prs) | |
of f, g by evaluating determinants of appropriately selected | |
submatrices of sylvester(f, g, x, 2). The dimensions of the | |
latter are (2*deg(f)) x (2*deg(f)). | |
Each coefficient is computed by evaluating the determinant of the | |
corresponding submatrix of sylvester(f, g, x, 2). | |
If the modified subresultant prs is complete, then the output coincides | |
with the Sturmian sequence of the polynomials f, g. | |
References: | |
=========== | |
1. A. G. Akritas,G.I. Malaschonok and P.S. Vigklas: | |
Sturm Sequences and Modified Subresultant Polynomial Remainder | |
Sequences. Serdica Journal of Computing, Vol. 8, No 1, 29--46, 2014. | |
""" | |
# make sure neither f nor g is 0 | |
if f == 0 or g == 0: | |
return [f, g] | |
n = degF = degree(f, x) | |
m = degG = degree(g, x) | |
# make sure proper degrees | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, degF, degG, f, g = m, n, degG, degF, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
SR_L = [f, g] # modified subresultant list | |
# form matrix sylvester(f, g, x, 2) | |
S = sylvester(f, g, x, 2) | |
# pick appropriate submatrices of S | |
# and form modified subresultant polys | |
j = m - 1 | |
while j > 0: | |
# delete last 2*j rows of pairs of coeffs of f, g | |
Sp = S[0:2*n - 2*j, :] # copy of first 2*n - 2*j rows of S | |
# evaluate determinants and form coefficients list | |
coeff_L, k, l = [], Sp.rows, 0 | |
while l <= j: | |
coeff_L.append(Sp[:, 0:k].det()) | |
Sp.col_swap(k - 1, k + l) | |
l += 1 | |
# form poly and append to SP_L | |
SR_L.append(Poly(coeff_L, x).as_expr()) | |
j -= 1 | |
# j = 0 | |
SR_L.append(S.det()) | |
return process_matrix_output(SR_L, x) | |
def res(f, g, x): | |
""" | |
The input polynomials f, g are in Z[x] or in Q[x]. | |
The output is the resultant of f, g computed by evaluating | |
the determinant of the matrix sylvester(f, g, x, 1). | |
References: | |
=========== | |
1. J. S. Cohen: Computer Algebra and Symbolic Computation | |
- Mathematical Methods. A. K. Peters, 2003. | |
""" | |
if f == 0 or g == 0: | |
raise PolynomialError("The resultant of %s and %s is not defined" % (f, g)) | |
else: | |
return sylvester(f, g, x, 1).det() | |
def res_q(f, g, x): | |
""" | |
The input polynomials f, g are in Z[x] or in Q[x]. | |
The output is the resultant of f, g computed recursively | |
by polynomial divisions in Q[x], using the function rem. | |
See Cohen's book p. 281. | |
References: | |
=========== | |
1. J. S. Cohen: Computer Algebra and Symbolic Computation | |
- Mathematical Methods. A. K. Peters, 2003. | |
""" | |
m = degree(f, x) | |
n = degree(g, x) | |
if m < n: | |
return (-1)**(m*n) * res_q(g, f, x) | |
elif n == 0: # g is a constant | |
return g**m | |
else: | |
r = rem(f, g, x) | |
if r == 0: | |
return 0 | |
else: | |
s = degree(r, x) | |
l = LC(g, x) | |
return (-1)**(m*n) * l**(m-s)*res_q(g, r, x) | |
def res_z(f, g, x): | |
""" | |
The input polynomials f, g are in Z[x] or in Q[x]. | |
The output is the resultant of f, g computed recursively | |
by polynomial divisions in Z[x], using the function prem(). | |
See Cohen's book p. 283. | |
References: | |
=========== | |
1. J. S. Cohen: Computer Algebra and Symbolic Computation | |
- Mathematical Methods. A. K. Peters, 2003. | |
""" | |
m = degree(f, x) | |
n = degree(g, x) | |
if m < n: | |
return (-1)**(m*n) * res_z(g, f, x) | |
elif n == 0: # g is a constant | |
return g**m | |
else: | |
r = prem(f, g, x) | |
if r == 0: | |
return 0 | |
else: | |
delta = m - n + 1 | |
w = (-1)**(m*n) * res_z(g, r, x) | |
s = degree(r, x) | |
l = LC(g, x) | |
k = delta * n - m + s | |
return quo(w, l**k, x) | |
def sign_seq(poly_seq, x): | |
""" | |
Given a sequence of polynomials poly_seq, it returns | |
the sequence of signs of the leading coefficients of | |
the polynomials in poly_seq. | |
""" | |
return [sign(LC(poly_seq[i], x)) for i in range(len(poly_seq))] | |
def bezout(p, q, x, method='bz'): | |
""" | |
The input polynomials p, q are in Z[x] or in Q[x]. Let | |
mx = max(degree(p, x), degree(q, x)). | |
The default option bezout(p, q, x, method='bz') returns Bezout's | |
symmetric matrix of p and q, of dimensions (mx) x (mx). The | |
determinant of this matrix is equal to the determinant of sylvester2, | |
Sylvester's matrix of 1853, whose dimensions are (2*mx) x (2*mx); | |
however the subresultants of these two matrices may differ. | |
The other option, bezout(p, q, x, 'prs'), is of interest to us | |
in this module because it returns a matrix equivalent to sylvester2. | |
In this case all subresultants of the two matrices are identical. | |
Both the subresultant polynomial remainder sequence (prs) and | |
the modified subresultant prs of p and q can be computed by | |
evaluating determinants of appropriately selected submatrices of | |
bezout(p, q, x, 'prs') --- one determinant per coefficient of the | |
remainder polynomials. | |
The matrices bezout(p, q, x, 'bz') and bezout(p, q, x, 'prs') | |
are related by the formula | |
bezout(p, q, x, 'prs') = | |
backward_eye(deg(p)) * bezout(p, q, x, 'bz') * backward_eye(deg(p)), | |
where backward_eye() is the backward identity function. | |
References | |
========== | |
1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants | |
and Their Applications. Appl. Algebra in Engin., Communic. and Comp., | |
Vol. 15, 233-266, 2004. | |
""" | |
# obtain degrees of polys | |
m, n = degree( Poly(p, x), x), degree( Poly(q, x), x) | |
# Special cases: | |
# A:: case m = n < 0 (i.e. both polys are 0) | |
if m == n and n < 0: | |
return Matrix([]) | |
# B:: case m = n = 0 (i.e. both polys are constants) | |
if m == n and n == 0: | |
return Matrix([]) | |
# C:: m == 0 and n < 0 or m < 0 and n == 0 | |
# (i.e. one poly is constant and the other is 0) | |
if m == 0 and n < 0: | |
return Matrix([]) | |
elif m < 0 and n == 0: | |
return Matrix([]) | |
# D:: m >= 1 and n < 0 or m < 0 and n >=1 | |
# (i.e. one poly is of degree >=1 and the other is 0) | |
if m >= 1 and n < 0: | |
return Matrix([0]) | |
elif m < 0 and n >= 1: | |
return Matrix([0]) | |
y = var('y') | |
# expr is 0 when x = y | |
expr = p * q.subs({x:y}) - p.subs({x:y}) * q | |
# hence expr is exactly divisible by x - y | |
poly = Poly( quo(expr, x-y), x, y) | |
# form Bezout matrix and store them in B as indicated to get | |
# the LC coefficient of each poly either in the first position | |
# of each row (method='prs') or in the last (method='bz'). | |
mx = max(m, n) | |
B = zeros(mx) | |
for i in range(mx): | |
for j in range(mx): | |
if method == 'prs': | |
B[mx - 1 - i, mx - 1 - j] = poly.nth(i, j) | |
else: | |
B[i, j] = poly.nth(i, j) | |
return B | |
def backward_eye(n): | |
''' | |
Returns the backward identity matrix of dimensions n x n. | |
Needed to "turn" the Bezout matrices | |
so that the leading coefficients are first. | |
See docstring of the function bezout(p, q, x, method='bz'). | |
''' | |
M = eye(n) # identity matrix of order n | |
for i in range(int(M.rows / 2)): | |
M.row_swap(0 + i, M.rows - 1 - i) | |
return M | |
def subresultants_bezout(p, q, x): | |
""" | |
The input polynomials p, q are in Z[x] or in Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the subresultant polynomial remainder sequence | |
of p, q by evaluating determinants of appropriately selected | |
submatrices of bezout(p, q, x, 'prs'). The dimensions of the | |
latter are deg(p) x deg(p). | |
Each coefficient is computed by evaluating the determinant of the | |
corresponding submatrix of bezout(p, q, x, 'prs'). | |
bezout(p, q, x, 'prs) is used instead of sylvester(p, q, x, 1), | |
Sylvester's matrix of 1840, because the dimensions of the latter | |
are (deg(p) + deg(q)) x (deg(p) + deg(q)). | |
If the subresultant prs is complete, then the output coincides | |
with the Euclidean sequence of the polynomials p, q. | |
References | |
========== | |
1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants | |
and Their Applications. Appl. Algebra in Engin., Communic. and Comp., | |
Vol. 15, 233-266, 2004. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
f, g = p, q | |
n = degF = degree(f, x) | |
m = degG = degree(g, x) | |
# make sure proper degrees | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, degF, degG, f, g = m, n, degG, degF, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
SR_L = [f, g] # subresultant list | |
F = LC(f, x)**(degF - degG) | |
# form the bezout matrix | |
B = bezout(f, g, x, 'prs') | |
# pick appropriate submatrices of B | |
# and form subresultant polys | |
if degF > degG: | |
j = 2 | |
if degF == degG: | |
j = 1 | |
while j <= degF: | |
M = B[0:j, :] | |
k, coeff_L = j - 1, [] | |
while k <= degF - 1: | |
coeff_L.append(M[:, 0:j].det()) | |
if k < degF - 1: | |
M.col_swap(j - 1, k + 1) | |
k = k + 1 | |
# apply Theorem 2.1 in the paper by Toca & Vega 2004 | |
# to get correct signs | |
SR_L.append(int((-1)**(j*(j-1)/2)) * (Poly(coeff_L, x) / F).as_expr()) | |
j = j + 1 | |
return process_matrix_output(SR_L, x) | |
def modified_subresultants_bezout(p, q, x): | |
""" | |
The input polynomials p, q are in Z[x] or in Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the modified subresultant polynomial remainder sequence | |
of p, q by evaluating determinants of appropriately selected | |
submatrices of bezout(p, q, x, 'prs'). The dimensions of the | |
latter are deg(p) x deg(p). | |
Each coefficient is computed by evaluating the determinant of the | |
corresponding submatrix of bezout(p, q, x, 'prs'). | |
bezout(p, q, x, 'prs') is used instead of sylvester(p, q, x, 2), | |
Sylvester's matrix of 1853, because the dimensions of the latter | |
are 2*deg(p) x 2*deg(p). | |
If the modified subresultant prs is complete, and LC( p ) > 0, the output | |
coincides with the (generalized) Sturm's sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
2. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants | |
and Their Applications. Appl. Algebra in Engin., Communic. and Comp., | |
Vol. 15, 233-266, 2004. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
f, g = p, q | |
n = degF = degree(f, x) | |
m = degG = degree(g, x) | |
# make sure proper degrees | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, degF, degG, f, g = m, n, degG, degF, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
SR_L = [f, g] # subresultant list | |
# form the bezout matrix | |
B = bezout(f, g, x, 'prs') | |
# pick appropriate submatrices of B | |
# and form subresultant polys | |
if degF > degG: | |
j = 2 | |
if degF == degG: | |
j = 1 | |
while j <= degF: | |
M = B[0:j, :] | |
k, coeff_L = j - 1, [] | |
while k <= degF - 1: | |
coeff_L.append(M[:, 0:j].det()) | |
if k < degF - 1: | |
M.col_swap(j - 1, k + 1) | |
k = k + 1 | |
## Theorem 2.1 in the paper by Toca & Vega 2004 is _not needed_ | |
## in this case since | |
## the bezout matrix is equivalent to sylvester2 | |
SR_L.append(( Poly(coeff_L, x)).as_expr()) | |
j = j + 1 | |
return process_matrix_output(SR_L, x) | |
def sturm_pg(p, q, x, method=0): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the (generalized) Sturm sequence of p and q in Z[x] or Q[x]. | |
If q = diff(p, x, 1) it is the usual Sturm sequence. | |
A. If method == 0, default, the remainder coefficients of the sequence | |
are (in absolute value) ``modified'' subresultants, which for non-monic | |
polynomials are greater than the coefficients of the corresponding | |
subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))). | |
B. If method == 1, the remainder coefficients of the sequence are (in | |
absolute value) subresultants, which for non-monic polynomials are | |
smaller than the coefficients of the corresponding ``modified'' | |
subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))). | |
If the Sturm sequence is complete, method=0 and LC( p ) > 0, the coefficients | |
of the polynomials in the sequence are ``modified'' subresultants. | |
That is, they are determinants of appropriately selected submatrices of | |
sylvester2, Sylvester's matrix of 1853. In this case the Sturm sequence | |
coincides with the ``modified'' subresultant prs, of the polynomials | |
p, q. | |
If the Sturm sequence is incomplete and method=0 then the signs of the | |
coefficients of the polynomials in the sequence may differ from the signs | |
of the coefficients of the corresponding polynomials in the ``modified'' | |
subresultant prs; however, the absolute values are the same. | |
To compute the coefficients, no determinant evaluation takes place. Instead, | |
polynomial divisions in Q[x] are performed, using the function rem(p, q, x); | |
the coefficients of the remainders computed this way become (``modified'') | |
subresultants with the help of the Pell-Gordon Theorem of 1917. | |
See also the function euclid_pg(p, q, x). | |
References | |
========== | |
1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding | |
the Highest Common Factor of Two Polynomials. Annals of MatheMatics, | |
Second Series, 18 (1917), No. 4, 188-193. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
d0 = degree(p, x) | |
d1 = degree(q, x) | |
if d0 == 0 and d1 == 0: | |
return [p, q] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
p, q = q, p | |
if d0 > 0 and d1 == 0: | |
return [p,q] | |
# make sure LC(p) > 0 | |
flag = 0 | |
if LC(p,x) < 0: | |
flag = 1 | |
p = -p | |
q = -q | |
# initialize | |
lcf = LC(p, x)**(d0 - d1) # lcf * subr = modified subr | |
a0, a1 = p, q # the input polys | |
sturm_seq = [a0, a1] # the output list | |
del0 = d0 - d1 # degree difference | |
rho1 = LC(a1, x) # leading coeff of a1 | |
exp_deg = d1 - 1 # expected degree of a2 | |
a2 = - rem(a0, a1, domain=QQ) # first remainder | |
rho2 = LC(a2,x) # leading coeff of a2 | |
d2 = degree(a2, x) # actual degree of a2 | |
deg_diff_new = exp_deg - d2 # expected - actual degree | |
del1 = d1 - d2 # degree difference | |
# mul_fac is the factor by which a2 is multiplied to | |
# get integer coefficients | |
mul_fac_old = rho1**(del0 + del1 - deg_diff_new) | |
# append accordingly | |
if method == 0: | |
sturm_seq.append( simplify(lcf * a2 * Abs(mul_fac_old))) | |
else: | |
sturm_seq.append( simplify( a2 * Abs(mul_fac_old))) | |
# main loop | |
deg_diff_old = deg_diff_new | |
while d2 > 0: | |
a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees | |
del0 = del1 # update degree difference | |
exp_deg = d1 - 1 # new expected degree | |
a2 = - rem(a0, a1, domain=QQ) # new remainder | |
rho3 = LC(a2, x) # leading coeff of a2 | |
d2 = degree(a2, x) # actual degree of a2 | |
deg_diff_new = exp_deg - d2 # expected - actual degree | |
del1 = d1 - d2 # degree difference | |
# take into consideration the power | |
# rho1**deg_diff_old that was "left out" | |
expo_old = deg_diff_old # rho1 raised to this power | |
expo_new = del0 + del1 - deg_diff_new # rho2 raised to this power | |
# update variables and append | |
mul_fac_new = rho2**(expo_new) * rho1**(expo_old) * mul_fac_old | |
deg_diff_old, mul_fac_old = deg_diff_new, mul_fac_new | |
rho1, rho2 = rho2, rho3 | |
if method == 0: | |
sturm_seq.append( simplify(lcf * a2 * Abs(mul_fac_old))) | |
else: | |
sturm_seq.append( simplify( a2 * Abs(mul_fac_old))) | |
if flag: # change the sign of the sequence | |
sturm_seq = [-i for i in sturm_seq] | |
# gcd is of degree > 0 ? | |
m = len(sturm_seq) | |
if sturm_seq[m - 1] == nan or sturm_seq[m - 1] == 0: | |
sturm_seq.pop(m - 1) | |
return sturm_seq | |
def sturm_q(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the (generalized) Sturm sequence of p and q in Q[x]. | |
Polynomial divisions in Q[x] are performed, using the function rem(p, q, x). | |
The coefficients of the polynomials in the Sturm sequence can be uniquely | |
determined from the corresponding coefficients of the polynomials found | |
either in: | |
(a) the ``modified'' subresultant prs, (references 1, 2) | |
or in | |
(b) the subresultant prs (reference 3). | |
References | |
========== | |
1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding | |
the Highest Common Factor of Two Polynomials. Annals of MatheMatics, | |
Second Series, 18 (1917), No. 4, 188-193. | |
2 Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
d0 = degree(p, x) | |
d1 = degree(q, x) | |
if d0 == 0 and d1 == 0: | |
return [p, q] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
p, q = q, p | |
if d0 > 0 and d1 == 0: | |
return [p,q] | |
# make sure LC(p) > 0 | |
flag = 0 | |
if LC(p,x) < 0: | |
flag = 1 | |
p = -p | |
q = -q | |
# initialize | |
a0, a1 = p, q # the input polys | |
sturm_seq = [a0, a1] # the output list | |
a2 = -rem(a0, a1, domain=QQ) # first remainder | |
d2 = degree(a2, x) # degree of a2 | |
sturm_seq.append( a2 ) | |
# main loop | |
while d2 > 0: | |
a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees | |
a2 = -rem(a0, a1, domain=QQ) # new remainder | |
d2 = degree(a2, x) # actual degree of a2 | |
sturm_seq.append( a2 ) | |
if flag: # change the sign of the sequence | |
sturm_seq = [-i for i in sturm_seq] | |
# gcd is of degree > 0 ? | |
m = len(sturm_seq) | |
if sturm_seq[m - 1] == nan or sturm_seq[m - 1] == 0: | |
sturm_seq.pop(m - 1) | |
return sturm_seq | |
def sturm_amv(p, q, x, method=0): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the (generalized) Sturm sequence of p and q in Z[x] or Q[x]. | |
If q = diff(p, x, 1) it is the usual Sturm sequence. | |
A. If method == 0, default, the remainder coefficients of the | |
sequence are (in absolute value) ``modified'' subresultants, which | |
for non-monic polynomials are greater than the coefficients of the | |
corresponding subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))). | |
B. If method == 1, the remainder coefficients of the sequence are (in | |
absolute value) subresultants, which for non-monic polynomials are | |
smaller than the coefficients of the corresponding ``modified'' | |
subresultants by the factor Abs( LC(p)**( deg(p)- deg(q)) ). | |
If the Sturm sequence is complete, method=0 and LC( p ) > 0, then the | |
coefficients of the polynomials in the sequence are ``modified'' subresultants. | |
That is, they are determinants of appropriately selected submatrices of | |
sylvester2, Sylvester's matrix of 1853. In this case the Sturm sequence | |
coincides with the ``modified'' subresultant prs, of the polynomials | |
p, q. | |
If the Sturm sequence is incomplete and method=0 then the signs of the | |
coefficients of the polynomials in the sequence may differ from the signs | |
of the coefficients of the corresponding polynomials in the ``modified'' | |
subresultant prs; however, the absolute values are the same. | |
To compute the coefficients, no determinant evaluation takes place. | |
Instead, we first compute the euclidean sequence of p and q using | |
euclid_amv(p, q, x) and then: (a) change the signs of the remainders in the | |
Euclidean sequence according to the pattern "-, -, +, +, -, -, +, +,..." | |
(see Lemma 1 in the 1st reference or Theorem 3 in the 2nd reference) | |
and (b) if method=0, assuming deg(p) > deg(q), we multiply the remainder | |
coefficients of the Euclidean sequence times the factor | |
Abs( LC(p)**( deg(p)- deg(q)) ) to make them modified subresultants. | |
See also the function sturm_pg(p, q, x). | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders | |
Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' Serdica | |
Journal of Computing 9(2) (2015), 123-138. | |
3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial | |
Remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].'' | |
Serdica Journal of Computing 10 (2016), No.3-4, 197-217. | |
""" | |
# compute the euclidean sequence | |
prs = euclid_amv(p, q, x) | |
# defensive | |
if prs == [] or len(prs) == 2: | |
return prs | |
# the coefficients in prs are subresultants and hence are smaller | |
# than the corresponding subresultants by the factor | |
# Abs( LC(prs[0])**( deg(prs[0]) - deg(prs[1])) ); Theorem 2, 2nd reference. | |
lcf = Abs( LC(prs[0])**( degree(prs[0], x) - degree(prs[1], x) ) ) | |
# the signs of the first two polys in the sequence stay the same | |
sturm_seq = [prs[0], prs[1]] | |
# change the signs according to "-, -, +, +, -, -, +, +,..." | |
# and multiply times lcf if needed | |
flag = 0 | |
m = len(prs) | |
i = 2 | |
while i <= m-1: | |
if flag == 0: | |
sturm_seq.append( - prs[i] ) | |
i = i + 1 | |
if i == m: | |
break | |
sturm_seq.append( - prs[i] ) | |
i = i + 1 | |
flag = 1 | |
elif flag == 1: | |
sturm_seq.append( prs[i] ) | |
i = i + 1 | |
if i == m: | |
break | |
sturm_seq.append( prs[i] ) | |
i = i + 1 | |
flag = 0 | |
# subresultants or modified subresultants? | |
if method == 0 and lcf > 1: | |
aux_seq = [sturm_seq[0], sturm_seq[1]] | |
for i in range(2, m): | |
aux_seq.append(simplify(sturm_seq[i] * lcf )) | |
sturm_seq = aux_seq | |
return sturm_seq | |
def euclid_pg(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the Euclidean sequence of p and q in Z[x] or Q[x]. | |
If the Euclidean sequence is complete the coefficients of the polynomials | |
in the sequence are subresultants. That is, they are determinants of | |
appropriately selected submatrices of sylvester1, Sylvester's matrix of 1840. | |
In this case the Euclidean sequence coincides with the subresultant prs | |
of the polynomials p, q. | |
If the Euclidean sequence is incomplete the signs of the coefficients of the | |
polynomials in the sequence may differ from the signs of the coefficients of | |
the corresponding polynomials in the subresultant prs; however, the absolute | |
values are the same. | |
To compute the Euclidean sequence, no determinant evaluation takes place. | |
We first compute the (generalized) Sturm sequence of p and q using | |
sturm_pg(p, q, x, 1), in which case the coefficients are (in absolute value) | |
equal to subresultants. Then we change the signs of the remainders in the | |
Sturm sequence according to the pattern "-, -, +, +, -, -, +, +,..." ; | |
see Lemma 1 in the 1st reference or Theorem 3 in the 2nd reference as well as | |
the function sturm_pg(p, q, x). | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders | |
Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' Serdica | |
Journal of Computing 9(2) (2015), 123-138. | |
3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial | |
Remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].'' | |
Serdica Journal of Computing 10 (2016), No.3-4, 197-217. | |
""" | |
# compute the sturmian sequence using the Pell-Gordon (or AMV) theorem | |
# with the coefficients in the prs being (in absolute value) subresultants | |
prs = sturm_pg(p, q, x, 1) ## any other method would do | |
# defensive | |
if prs == [] or len(prs) == 2: | |
return prs | |
# the signs of the first two polys in the sequence stay the same | |
euclid_seq = [prs[0], prs[1]] | |
# change the signs according to "-, -, +, +, -, -, +, +,..." | |
flag = 0 | |
m = len(prs) | |
i = 2 | |
while i <= m-1: | |
if flag == 0: | |
euclid_seq.append(- prs[i] ) | |
i = i + 1 | |
if i == m: | |
break | |
euclid_seq.append(- prs[i] ) | |
i = i + 1 | |
flag = 1 | |
elif flag == 1: | |
euclid_seq.append(prs[i] ) | |
i = i + 1 | |
if i == m: | |
break | |
euclid_seq.append(prs[i] ) | |
i = i + 1 | |
flag = 0 | |
return euclid_seq | |
def euclid_q(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the Euclidean sequence of p and q in Q[x]. | |
Polynomial divisions in Q[x] are performed, using the function rem(p, q, x). | |
The coefficients of the polynomials in the Euclidean sequence can be uniquely | |
determined from the corresponding coefficients of the polynomials found | |
either in: | |
(a) the ``modified'' subresultant polynomial remainder sequence, | |
(references 1, 2) | |
or in | |
(b) the subresultant polynomial remainder sequence (references 3). | |
References | |
========== | |
1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding | |
the Highest Common Factor of Two Polynomials. Annals of MatheMatics, | |
Second Series, 18 (1917), No. 4, 188-193. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
d0 = degree(p, x) | |
d1 = degree(q, x) | |
if d0 == 0 and d1 == 0: | |
return [p, q] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
p, q = q, p | |
if d0 > 0 and d1 == 0: | |
return [p,q] | |
# make sure LC(p) > 0 | |
flag = 0 | |
if LC(p,x) < 0: | |
flag = 1 | |
p = -p | |
q = -q | |
# initialize | |
a0, a1 = p, q # the input polys | |
euclid_seq = [a0, a1] # the output list | |
a2 = rem(a0, a1, domain=QQ) # first remainder | |
d2 = degree(a2, x) # degree of a2 | |
euclid_seq.append( a2 ) | |
# main loop | |
while d2 > 0: | |
a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees | |
a2 = rem(a0, a1, domain=QQ) # new remainder | |
d2 = degree(a2, x) # actual degree of a2 | |
euclid_seq.append( a2 ) | |
if flag: # change the sign of the sequence | |
euclid_seq = [-i for i in euclid_seq] | |
# gcd is of degree > 0 ? | |
m = len(euclid_seq) | |
if euclid_seq[m - 1] == nan or euclid_seq[m - 1] == 0: | |
euclid_seq.pop(m - 1) | |
return euclid_seq | |
def euclid_amv(f, g, x): | |
""" | |
f, g are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(f, x) >= degree(g, x). | |
Computes the Euclidean sequence of p and q in Z[x] or Q[x]. | |
If the Euclidean sequence is complete the coefficients of the polynomials | |
in the sequence are subresultants. That is, they are determinants of | |
appropriately selected submatrices of sylvester1, Sylvester's matrix of 1840. | |
In this case the Euclidean sequence coincides with the subresultant prs, | |
of the polynomials p, q. | |
If the Euclidean sequence is incomplete the signs of the coefficients of the | |
polynomials in the sequence may differ from the signs of the coefficients of | |
the corresponding polynomials in the subresultant prs; however, the absolute | |
values are the same. | |
To compute the coefficients, no determinant evaluation takes place. | |
Instead, polynomial divisions in Z[x] or Q[x] are performed, using | |
the function rem_z(f, g, x); the coefficients of the remainders | |
computed this way become subresultants with the help of the | |
Collins-Brown-Traub formula for coefficient reduction. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial | |
remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].'' | |
Serdica Journal of Computing 10 (2016), No.3-4, 197-217. | |
""" | |
# make sure neither f nor g is 0 | |
if f == 0 or g == 0: | |
return [f, g] | |
# make sure proper degrees | |
d0 = degree(f, x) | |
d1 = degree(g, x) | |
if d0 == 0 and d1 == 0: | |
return [f, g] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
f, g = g, f | |
if d0 > 0 and d1 == 0: | |
return [f, g] | |
# initialize | |
a0 = f | |
a1 = g | |
euclid_seq = [a0, a1] | |
deg_dif_p1, c = degree(a0, x) - degree(a1, x) + 1, -1 | |
# compute the first polynomial of the prs | |
i = 1 | |
a2 = rem_z(a0, a1, x) / Abs( (-1)**deg_dif_p1 ) # first remainder | |
euclid_seq.append( a2 ) | |
d2 = degree(a2, x) # actual degree of a2 | |
# main loop | |
while d2 >= 1: | |
a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees | |
i += 1 | |
sigma0 = -LC(a0) | |
c = (sigma0**(deg_dif_p1 - 1)) / (c**(deg_dif_p1 - 2)) | |
deg_dif_p1 = degree(a0, x) - d2 + 1 | |
a2 = rem_z(a0, a1, x) / Abs( (c**(deg_dif_p1 - 1)) * sigma0 ) | |
euclid_seq.append( a2 ) | |
d2 = degree(a2, x) # actual degree of a2 | |
# gcd is of degree > 0 ? | |
m = len(euclid_seq) | |
if euclid_seq[m - 1] == nan or euclid_seq[m - 1] == 0: | |
euclid_seq.pop(m - 1) | |
return euclid_seq | |
def modified_subresultants_pg(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the ``modified'' subresultant prs of p and q in Z[x] or Q[x]; | |
the coefficients of the polynomials in the sequence are | |
``modified'' subresultants. That is, they are determinants of appropriately | |
selected submatrices of sylvester2, Sylvester's matrix of 1853. | |
To compute the coefficients, no determinant evaluation takes place. Instead, | |
polynomial divisions in Q[x] are performed, using the function rem(p, q, x); | |
the coefficients of the remainders computed this way become ``modified'' | |
subresultants with the help of the Pell-Gordon Theorem of 1917. | |
If the ``modified'' subresultant prs is complete, and LC( p ) > 0, it coincides | |
with the (generalized) Sturm sequence of the polynomials p, q. | |
References | |
========== | |
1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding | |
the Highest Common Factor of Two Polynomials. Annals of MatheMatics, | |
Second Series, 18 (1917), No. 4, 188-193. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
d0 = degree(p,x) | |
d1 = degree(q,x) | |
if d0 == 0 and d1 == 0: | |
return [p, q] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
p, q = q, p | |
if d0 > 0 and d1 == 0: | |
return [p,q] | |
# initialize | |
k = var('k') # index in summation formula | |
u_list = [] # of elements (-1)**u_i | |
subres_l = [p, q] # mod. subr. prs output list | |
a0, a1 = p, q # the input polys | |
del0 = d0 - d1 # degree difference | |
degdif = del0 # save it | |
rho_1 = LC(a0) # lead. coeff (a0) | |
# Initialize Pell-Gordon variables | |
rho_list_minus_1 = sign( LC(a0, x)) # sign of LC(a0) | |
rho1 = LC(a1, x) # leading coeff of a1 | |
rho_list = [ sign(rho1)] # of signs | |
p_list = [del0] # of degree differences | |
u = summation(k, (k, 1, p_list[0])) # value of u | |
u_list.append(u) # of u values | |
v = sum(p_list) # v value | |
# first remainder | |
exp_deg = d1 - 1 # expected degree of a2 | |
a2 = - rem(a0, a1, domain=QQ) # first remainder | |
rho2 = LC(a2, x) # leading coeff of a2 | |
d2 = degree(a2, x) # actual degree of a2 | |
deg_diff_new = exp_deg - d2 # expected - actual degree | |
del1 = d1 - d2 # degree difference | |
# mul_fac is the factor by which a2 is multiplied to | |
# get integer coefficients | |
mul_fac_old = rho1**(del0 + del1 - deg_diff_new) | |
# update Pell-Gordon variables | |
p_list.append(1 + deg_diff_new) # deg_diff_new is 0 for complete seq | |
# apply Pell-Gordon formula (7) in second reference | |
num = 1 # numerator of fraction | |
for u in u_list: | |
num *= (-1)**u | |
num = num * (-1)**v | |
# denominator depends on complete / incomplete seq | |
if deg_diff_new == 0: # complete seq | |
den = 1 | |
for k in range(len(rho_list)): | |
den *= rho_list[k]**(p_list[k] + p_list[k + 1]) | |
den = den * rho_list_minus_1 | |
else: # incomplete seq | |
den = 1 | |
for k in range(len(rho_list)-1): | |
den *= rho_list[k]**(p_list[k] + p_list[k + 1]) | |
den = den * rho_list_minus_1 | |
expo = (p_list[len(rho_list) - 1] + p_list[len(rho_list)] - deg_diff_new) | |
den = den * rho_list[len(rho_list) - 1]**expo | |
# the sign of the determinant depends on sg(num / den) | |
if sign(num / den) > 0: | |
subres_l.append( simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) ) | |
else: | |
subres_l.append(- simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) ) | |
# update Pell-Gordon variables | |
k = var('k') | |
rho_list.append( sign(rho2)) | |
u = summation(k, (k, 1, p_list[len(p_list) - 1])) | |
u_list.append(u) | |
v = sum(p_list) | |
deg_diff_old=deg_diff_new | |
# main loop | |
while d2 > 0: | |
a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees | |
del0 = del1 # update degree difference | |
exp_deg = d1 - 1 # new expected degree | |
a2 = - rem(a0, a1, domain=QQ) # new remainder | |
rho3 = LC(a2, x) # leading coeff of a2 | |
d2 = degree(a2, x) # actual degree of a2 | |
deg_diff_new = exp_deg - d2 # expected - actual degree | |
del1 = d1 - d2 # degree difference | |
# take into consideration the power | |
# rho1**deg_diff_old that was "left out" | |
expo_old = deg_diff_old # rho1 raised to this power | |
expo_new = del0 + del1 - deg_diff_new # rho2 raised to this power | |
mul_fac_new = rho2**(expo_new) * rho1**(expo_old) * mul_fac_old | |
# update variables | |
deg_diff_old, mul_fac_old = deg_diff_new, mul_fac_new | |
rho1, rho2 = rho2, rho3 | |
# update Pell-Gordon variables | |
p_list.append(1 + deg_diff_new) # deg_diff_new is 0 for complete seq | |
# apply Pell-Gordon formula (7) in second reference | |
num = 1 # numerator | |
for u in u_list: | |
num *= (-1)**u | |
num = num * (-1)**v | |
# denominator depends on complete / incomplete seq | |
if deg_diff_new == 0: # complete seq | |
den = 1 | |
for k in range(len(rho_list)): | |
den *= rho_list[k]**(p_list[k] + p_list[k + 1]) | |
den = den * rho_list_minus_1 | |
else: # incomplete seq | |
den = 1 | |
for k in range(len(rho_list)-1): | |
den *= rho_list[k]**(p_list[k] + p_list[k + 1]) | |
den = den * rho_list_minus_1 | |
expo = (p_list[len(rho_list) - 1] + p_list[len(rho_list)] - deg_diff_new) | |
den = den * rho_list[len(rho_list) - 1]**expo | |
# the sign of the determinant depends on sg(num / den) | |
if sign(num / den) > 0: | |
subres_l.append( simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) ) | |
else: | |
subres_l.append(- simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) ) | |
# update Pell-Gordon variables | |
k = var('k') | |
rho_list.append( sign(rho2)) | |
u = summation(k, (k, 1, p_list[len(p_list) - 1])) | |
u_list.append(u) | |
v = sum(p_list) | |
# gcd is of degree > 0 ? | |
m = len(subres_l) | |
if subres_l[m - 1] == nan or subres_l[m - 1] == 0: | |
subres_l.pop(m - 1) | |
# LC( p ) < 0 | |
m = len(subres_l) # list may be shorter now due to deg(gcd ) > 0 | |
if LC( p ) < 0: | |
aux_seq = [subres_l[0], subres_l[1]] | |
for i in range(2, m): | |
aux_seq.append(simplify(subres_l[i] * (-1) )) | |
subres_l = aux_seq | |
return subres_l | |
def subresultants_pg(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the subresultant prs of p and q in Z[x] or Q[x], from | |
the modified subresultant prs of p and q. | |
The coefficients of the polynomials in these two sequences differ only | |
in sign and the factor LC(p)**( deg(p)- deg(q)) as stated in | |
Theorem 2 of the reference. | |
The coefficients of the polynomials in the output sequence are | |
subresultants. That is, they are determinants of appropriately | |
selected submatrices of sylvester1, Sylvester's matrix of 1840. | |
If the subresultant prs is complete, then it coincides with the | |
Euclidean sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: "On the Remainders | |
Obtained in Finding the Greatest Common Divisor of Two Polynomials." | |
Serdica Journal of Computing 9(2) (2015), 123-138. | |
""" | |
# compute the modified subresultant prs | |
lst = modified_subresultants_pg(p,q,x) ## any other method would do | |
# defensive | |
if lst == [] or len(lst) == 2: | |
return lst | |
# the coefficients in lst are modified subresultants and, hence, are | |
# greater than those of the corresponding subresultants by the factor | |
# LC(lst[0])**( deg(lst[0]) - deg(lst[1])); see Theorem 2 in reference. | |
lcf = LC(lst[0])**( degree(lst[0], x) - degree(lst[1], x) ) | |
# Initialize the subresultant prs list | |
subr_seq = [lst[0], lst[1]] | |
# compute the degree sequences m_i and j_i of Theorem 2 in reference. | |
deg_seq = [degree(Poly(poly, x), x) for poly in lst] | |
deg = deg_seq[0] | |
deg_seq_s = deg_seq[1:-1] | |
m_seq = [m-1 for m in deg_seq_s] | |
j_seq = [deg - m for m in m_seq] | |
# compute the AMV factors of Theorem 2 in reference. | |
fact = [(-1)**( j*(j-1)/S(2) ) for j in j_seq] | |
# shortened list without the first two polys | |
lst_s = lst[2:] | |
# poly lst_s[k] is multiplied times fact[k], divided by lcf | |
# and appended to the subresultant prs list | |
m = len(fact) | |
for k in range(m): | |
if sign(fact[k]) == -1: | |
subr_seq.append(-lst_s[k] / lcf) | |
else: | |
subr_seq.append(lst_s[k] / lcf) | |
return subr_seq | |
def subresultants_amv_q(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the subresultant prs of p and q in Q[x]; | |
the coefficients of the polynomials in the sequence are | |
subresultants. That is, they are determinants of appropriately | |
selected submatrices of sylvester1, Sylvester's matrix of 1840. | |
To compute the coefficients, no determinant evaluation takes place. | |
Instead, polynomial divisions in Q[x] are performed, using the | |
function rem(p, q, x); the coefficients of the remainders | |
computed this way become subresultants with the help of the | |
Akritas-Malaschonok-Vigklas Theorem of 2015. | |
If the subresultant prs is complete, then it coincides with the | |
Euclidean sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial | |
remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].'' | |
Serdica Journal of Computing 10 (2016), No.3-4, 197-217. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
d0 = degree(p, x) | |
d1 = degree(q, x) | |
if d0 == 0 and d1 == 0: | |
return [p, q] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
p, q = q, p | |
if d0 > 0 and d1 == 0: | |
return [p, q] | |
# initialize | |
i, s = 0, 0 # counters for remainders & odd elements | |
p_odd_index_sum = 0 # contains the sum of p_1, p_3, etc | |
subres_l = [p, q] # subresultant prs output list | |
a0, a1 = p, q # the input polys | |
sigma1 = LC(a1, x) # leading coeff of a1 | |
p0 = d0 - d1 # degree difference | |
if p0 % 2 == 1: | |
s += 1 | |
phi = floor( (s + 1) / 2 ) | |
mul_fac = 1 | |
d2 = d1 | |
# main loop | |
while d2 > 0: | |
i += 1 | |
a2 = rem(a0, a1, domain= QQ) # new remainder | |
if i == 1: | |
sigma2 = LC(a2, x) | |
else: | |
sigma3 = LC(a2, x) | |
sigma1, sigma2 = sigma2, sigma3 | |
d2 = degree(a2, x) | |
p1 = d1 - d2 | |
psi = i + phi + p_odd_index_sum | |
# new mul_fac | |
mul_fac = sigma1**(p0 + 1) * mul_fac | |
## compute the sign of the first fraction in formula (9) of the paper | |
# numerator | |
num = (-1)**psi | |
# denominator | |
den = sign(mul_fac) | |
# the sign of the determinant depends on sign( num / den ) != 0 | |
if sign(num / den) > 0: | |
subres_l.append( simplify(expand(a2* Abs(mul_fac)))) | |
else: | |
subres_l.append(- simplify(expand(a2* Abs(mul_fac)))) | |
## bring into mul_fac the missing power of sigma if there was a degree gap | |
if p1 - 1 > 0: | |
mul_fac = mul_fac * sigma1**(p1 - 1) | |
# update AMV variables | |
a0, a1, d0, d1 = a1, a2, d1, d2 | |
p0 = p1 | |
if p0 % 2 ==1: | |
s += 1 | |
phi = floor( (s + 1) / 2 ) | |
if i%2 == 1: | |
p_odd_index_sum += p0 # p_i has odd index | |
# gcd is of degree > 0 ? | |
m = len(subres_l) | |
if subres_l[m - 1] == nan or subres_l[m - 1] == 0: | |
subres_l.pop(m - 1) | |
return subres_l | |
def compute_sign(base, expo): | |
''' | |
base != 0 and expo >= 0 are integers; | |
returns the sign of base**expo without | |
evaluating the power itself! | |
''' | |
sb = sign(base) | |
if sb == 1: | |
return 1 | |
pe = expo % 2 | |
if pe == 0: | |
return -sb | |
else: | |
return sb | |
def rem_z(p, q, x): | |
''' | |
Intended mainly for p, q polynomials in Z[x] so that, | |
on dividing p by q, the remainder will also be in Z[x]. (However, | |
it also works fine for polynomials in Q[x].) It is assumed | |
that degree(p, x) >= degree(q, x). | |
It premultiplies p by the _absolute_ value of the leading coefficient | |
of q, raised to the power deg(p) - deg(q) + 1 and then performs | |
polynomial division in Q[x], using the function rem(p, q, x). | |
By contrast the function prem(p, q, x) does _not_ use the absolute | |
value of the leading coefficient of q. | |
This results not only in ``messing up the signs'' of the Euclidean and | |
Sturmian prs's as mentioned in the second reference, | |
but also in violation of the main results of the first and third | |
references --- Theorem 4 and Theorem 1 respectively. Theorems 4 and 1 | |
establish a one-to-one correspondence between the Euclidean and the | |
Sturmian prs of p, q, on one hand, and the subresultant prs of p, q, | |
on the other. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders | |
Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' | |
Serdica Journal of Computing, 9(2) (2015), 123-138. | |
2. https://planetMath.org/sturmstheorem | |
3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result on | |
the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
''' | |
if (p.as_poly().is_univariate and q.as_poly().is_univariate and | |
p.as_poly().gens == q.as_poly().gens): | |
delta = (degree(p, x) - degree(q, x) + 1) | |
return rem(Abs(LC(q, x))**delta * p, q, x) | |
else: | |
return prem(p, q, x) | |
def quo_z(p, q, x): | |
""" | |
Intended mainly for p, q polynomials in Z[x] so that, | |
on dividing p by q, the quotient will also be in Z[x]. (However, | |
it also works fine for polynomials in Q[x].) It is assumed | |
that degree(p, x) >= degree(q, x). | |
It premultiplies p by the _absolute_ value of the leading coefficient | |
of q, raised to the power deg(p) - deg(q) + 1 and then performs | |
polynomial division in Q[x], using the function quo(p, q, x). | |
By contrast the function pquo(p, q, x) does _not_ use the absolute | |
value of the leading coefficient of q. | |
See also function rem_z(p, q, x) for additional comments and references. | |
""" | |
if (p.as_poly().is_univariate and q.as_poly().is_univariate and | |
p.as_poly().gens == q.as_poly().gens): | |
delta = (degree(p, x) - degree(q, x) + 1) | |
return quo(Abs(LC(q, x))**delta * p, q, x) | |
else: | |
return pquo(p, q, x) | |
def subresultants_amv(f, g, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(f, x) >= degree(g, x). | |
Computes the subresultant prs of p and q in Z[x] or Q[x]; | |
the coefficients of the polynomials in the sequence are | |
subresultants. That is, they are determinants of appropriately | |
selected submatrices of sylvester1, Sylvester's matrix of 1840. | |
To compute the coefficients, no determinant evaluation takes place. | |
Instead, polynomial divisions in Z[x] or Q[x] are performed, using | |
the function rem_z(p, q, x); the coefficients of the remainders | |
computed this way become subresultants with the help of the | |
Akritas-Malaschonok-Vigklas Theorem of 2015 and the Collins-Brown- | |
Traub formula for coefficient reduction. | |
If the subresultant prs is complete, then it coincides with the | |
Euclidean sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result | |
on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial | |
remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].'' | |
Serdica Journal of Computing 10 (2016), No.3-4, 197-217. | |
""" | |
# make sure neither f nor g is 0 | |
if f == 0 or g == 0: | |
return [f, g] | |
# make sure proper degrees | |
d0 = degree(f, x) | |
d1 = degree(g, x) | |
if d0 == 0 and d1 == 0: | |
return [f, g] | |
if d1 > d0: | |
d0, d1 = d1, d0 | |
f, g = g, f | |
if d0 > 0 and d1 == 0: | |
return [f, g] | |
# initialize | |
a0 = f | |
a1 = g | |
subres_l = [a0, a1] | |
deg_dif_p1, c = degree(a0, x) - degree(a1, x) + 1, -1 | |
# initialize AMV variables | |
sigma1 = LC(a1, x) # leading coeff of a1 | |
i, s = 0, 0 # counters for remainders & odd elements | |
p_odd_index_sum = 0 # contains the sum of p_1, p_3, etc | |
p0 = deg_dif_p1 - 1 | |
if p0 % 2 == 1: | |
s += 1 | |
phi = floor( (s + 1) / 2 ) | |
# compute the first polynomial of the prs | |
i += 1 | |
a2 = rem_z(a0, a1, x) / Abs( (-1)**deg_dif_p1 ) # first remainder | |
sigma2 = LC(a2, x) # leading coeff of a2 | |
d2 = degree(a2, x) # actual degree of a2 | |
p1 = d1 - d2 # degree difference | |
# sgn_den is the factor, the denominator 1st fraction of (9), | |
# by which a2 is multiplied to get integer coefficients | |
sgn_den = compute_sign( sigma1, p0 + 1 ) | |
## compute sign of the 1st fraction in formula (9) of the paper | |
# numerator | |
psi = i + phi + p_odd_index_sum | |
num = (-1)**psi | |
# denominator | |
den = sgn_den | |
# the sign of the determinant depends on sign(num / den) != 0 | |
if sign(num / den) > 0: | |
subres_l.append( a2 ) | |
else: | |
subres_l.append( -a2 ) | |
# update AMV variable | |
if p1 % 2 == 1: | |
s += 1 | |
# bring in the missing power of sigma if there was gap | |
if p1 - 1 > 0: | |
sgn_den = sgn_den * compute_sign( sigma1, p1 - 1 ) | |
# main loop | |
while d2 >= 1: | |
phi = floor( (s + 1) / 2 ) | |
if i%2 == 1: | |
p_odd_index_sum += p1 # p_i has odd index | |
a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees | |
p0 = p1 # update degree difference | |
i += 1 | |
sigma0 = -LC(a0) | |
c = (sigma0**(deg_dif_p1 - 1)) / (c**(deg_dif_p1 - 2)) | |
deg_dif_p1 = degree(a0, x) - d2 + 1 | |
a2 = rem_z(a0, a1, x) / Abs( (c**(deg_dif_p1 - 1)) * sigma0 ) | |
sigma3 = LC(a2, x) # leading coeff of a2 | |
d2 = degree(a2, x) # actual degree of a2 | |
p1 = d1 - d2 # degree difference | |
psi = i + phi + p_odd_index_sum | |
# update variables | |
sigma1, sigma2 = sigma2, sigma3 | |
# new sgn_den | |
sgn_den = compute_sign( sigma1, p0 + 1 ) * sgn_den | |
# compute the sign of the first fraction in formula (9) of the paper | |
# numerator | |
num = (-1)**psi | |
# denominator | |
den = sgn_den | |
# the sign of the determinant depends on sign( num / den ) != 0 | |
if sign(num / den) > 0: | |
subres_l.append( a2 ) | |
else: | |
subres_l.append( -a2 ) | |
# update AMV variable | |
if p1 % 2 ==1: | |
s += 1 | |
# bring in the missing power of sigma if there was gap | |
if p1 - 1 > 0: | |
sgn_den = sgn_den * compute_sign( sigma1, p1 - 1 ) | |
# gcd is of degree > 0 ? | |
m = len(subres_l) | |
if subres_l[m - 1] == nan or subres_l[m - 1] == 0: | |
subres_l.pop(m - 1) | |
return subres_l | |
def modified_subresultants_amv(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the modified subresultant prs of p and q in Z[x] or Q[x], | |
from the subresultant prs of p and q. | |
The coefficients of the polynomials in the two sequences differ only | |
in sign and the factor LC(p)**( deg(p)- deg(q)) as stated in | |
Theorem 2 of the reference. | |
The coefficients of the polynomials in the output sequence are | |
modified subresultants. That is, they are determinants of appropriately | |
selected submatrices of sylvester2, Sylvester's matrix of 1853. | |
If the modified subresultant prs is complete, and LC( p ) > 0, it coincides | |
with the (generalized) Sturm's sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: "On the Remainders | |
Obtained in Finding the Greatest Common Divisor of Two Polynomials." | |
Serdica Journal of Computing, Serdica Journal of Computing, 9(2) (2015), 123-138. | |
""" | |
# compute the subresultant prs | |
lst = subresultants_amv(p,q,x) ## any other method would do | |
# defensive | |
if lst == [] or len(lst) == 2: | |
return lst | |
# the coefficients in lst are subresultants and, hence, smaller than those | |
# of the corresponding modified subresultants by the factor | |
# LC(lst[0])**( deg(lst[0]) - deg(lst[1])); see Theorem 2. | |
lcf = LC(lst[0])**( degree(lst[0], x) - degree(lst[1], x) ) | |
# Initialize the modified subresultant prs list | |
subr_seq = [lst[0], lst[1]] | |
# compute the degree sequences m_i and j_i of Theorem 2 | |
deg_seq = [degree(Poly(poly, x), x) for poly in lst] | |
deg = deg_seq[0] | |
deg_seq_s = deg_seq[1:-1] | |
m_seq = [m-1 for m in deg_seq_s] | |
j_seq = [deg - m for m in m_seq] | |
# compute the AMV factors of Theorem 2 | |
fact = [(-1)**( j*(j-1)/S(2) ) for j in j_seq] | |
# shortened list without the first two polys | |
lst_s = lst[2:] | |
# poly lst_s[k] is multiplied times fact[k] and times lcf | |
# and appended to the subresultant prs list | |
m = len(fact) | |
for k in range(m): | |
if sign(fact[k]) == -1: | |
subr_seq.append( simplify(-lst_s[k] * lcf) ) | |
else: | |
subr_seq.append( simplify(lst_s[k] * lcf) ) | |
return subr_seq | |
def correct_sign(deg_f, deg_g, s1, rdel, cdel): | |
""" | |
Used in various subresultant prs algorithms. | |
Evaluates the determinant, (a.k.a. subresultant) of a properly selected | |
submatrix of s1, Sylvester's matrix of 1840, to get the correct sign | |
and value of the leading coefficient of a given polynomial remainder. | |
deg_f, deg_g are the degrees of the original polynomials p, q for which the | |
matrix s1 = sylvester(p, q, x, 1) was constructed. | |
rdel denotes the expected degree of the remainder; it is the number of | |
rows to be deleted from each group of rows in s1 as described in the | |
reference below. | |
cdel denotes the expected degree minus the actual degree of the remainder; | |
it is the number of columns to be deleted --- starting with the last column | |
forming the square matrix --- from the matrix resulting after the row deletions. | |
References | |
========== | |
Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences | |
and Modified Subresultant Polynomial Remainder Sequences.'' | |
Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014. | |
""" | |
M = s1[:, :] # copy of matrix s1 | |
# eliminate rdel rows from the first deg_g rows | |
for i in range(M.rows - deg_f - 1, M.rows - deg_f - rdel - 1, -1): | |
M.row_del(i) | |
# eliminate rdel rows from the last deg_f rows | |
for i in range(M.rows - 1, M.rows - rdel - 1, -1): | |
M.row_del(i) | |
# eliminate cdel columns | |
for i in range(cdel): | |
M.col_del(M.rows - 1) | |
# define submatrix | |
Md = M[:, 0: M.rows] | |
return Md.det() | |
def subresultants_rem(p, q, x): | |
""" | |
p, q are polynomials in Z[x] or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the subresultant prs of p and q in Z[x] or Q[x]; | |
the coefficients of the polynomials in the sequence are | |
subresultants. That is, they are determinants of appropriately | |
selected submatrices of sylvester1, Sylvester's matrix of 1840. | |
To compute the coefficients polynomial divisions in Q[x] are | |
performed, using the function rem(p, q, x). The coefficients | |
of the remainders computed this way become subresultants by evaluating | |
one subresultant per remainder --- that of the leading coefficient. | |
This way we obtain the correct sign and value of the leading coefficient | |
of the remainder and we easily ``force'' the rest of the coefficients | |
to become subresultants. | |
If the subresultant prs is complete, then it coincides with the | |
Euclidean sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G.:``Three New Methods for Computing Subresultant | |
Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
f, g = p, q | |
n = deg_f = degree(f, x) | |
m = deg_g = degree(g, x) | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
# initialize | |
s1 = sylvester(f, g, x, 1) | |
sr_list = [f, g] # subresultant list | |
# main loop | |
while deg_g > 0: | |
r = rem(p, q, x) | |
d = degree(r, x) | |
if d < 0: | |
return sr_list | |
# make coefficients subresultants evaluating ONE determinant | |
exp_deg = deg_g - 1 # expected degree | |
sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d) | |
r = simplify((r / LC(r, x)) * sign_value) | |
# append poly with subresultant coeffs | |
sr_list.append(r) | |
# update degrees and polys | |
deg_f, deg_g = deg_g, d | |
p, q = q, r | |
# gcd is of degree > 0 ? | |
m = len(sr_list) | |
if sr_list[m - 1] == nan or sr_list[m - 1] == 0: | |
sr_list.pop(m - 1) | |
return sr_list | |
def pivot(M, i, j): | |
''' | |
M is a matrix, and M[i, j] specifies the pivot element. | |
All elements below M[i, j], in the j-th column, will | |
be zeroed, if they are not already 0, according to | |
Dodgson-Bareiss' integer preserving transformations. | |
References | |
========== | |
1. Akritas, A. G.: ``A new method for computing polynomial greatest | |
common divisors and polynomial remainder sequences.'' | |
Numerische MatheMatik 52, 119-127, 1988. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem | |
by Van Vleck Regarding Sturm Sequences.'' | |
Serdica Journal of Computing, 7, No 4, 101-134, 2013. | |
''' | |
ma = M[:, :] # copy of matrix M | |
rs = ma.rows # No. of rows | |
cs = ma.cols # No. of cols | |
for r in range(i+1, rs): | |
if ma[r, j] != 0: | |
for c in range(j + 1, cs): | |
ma[r, c] = ma[i, j] * ma[r, c] - ma[i, c] * ma[r, j] | |
ma[r, j] = 0 | |
return ma | |
def rotate_r(L, k): | |
''' | |
Rotates right by k. L is a row of a matrix or a list. | |
''' | |
ll = list(L) | |
if ll == []: | |
return [] | |
for i in range(k): | |
el = ll.pop(len(ll) - 1) | |
ll.insert(0, el) | |
return ll if isinstance(L, list) else Matrix([ll]) | |
def rotate_l(L, k): | |
''' | |
Rotates left by k. L is a row of a matrix or a list. | |
''' | |
ll = list(L) | |
if ll == []: | |
return [] | |
for i in range(k): | |
el = ll.pop(0) | |
ll.insert(len(ll) - 1, el) | |
return ll if isinstance(L, list) else Matrix([ll]) | |
def row2poly(row, deg, x): | |
''' | |
Converts the row of a matrix to a poly of degree deg and variable x. | |
Some entries at the beginning and/or at the end of the row may be zero. | |
''' | |
k = 0 | |
poly = [] | |
leng = len(row) | |
# find the beginning of the poly ; i.e. the first | |
# non-zero element of the row | |
while row[k] == 0: | |
k = k + 1 | |
# append the next deg + 1 elements to poly | |
for j in range( deg + 1): | |
if k + j <= leng: | |
poly.append(row[k + j]) | |
return Poly(poly, x) | |
def create_ma(deg_f, deg_g, row1, row2, col_num): | |
''' | |
Creates a ``small'' matrix M to be triangularized. | |
deg_f, deg_g are the degrees of the divident and of the | |
divisor polynomials respectively, deg_g > deg_f. | |
The coefficients of the divident poly are the elements | |
in row2 and those of the divisor poly are the elements | |
in row1. | |
col_num defines the number of columns of the matrix M. | |
''' | |
if deg_g - deg_f >= 1: | |
print('Reverse degrees') | |
return | |
m = zeros(deg_f - deg_g + 2, col_num) | |
for i in range(deg_f - deg_g + 1): | |
m[i, :] = rotate_r(row1, i) | |
m[deg_f - deg_g + 1, :] = row2 | |
return m | |
def find_degree(M, deg_f): | |
''' | |
Finds the degree of the poly corresponding (after triangularization) | |
to the _last_ row of the ``small'' matrix M, created by create_ma(). | |
deg_f is the degree of the divident poly. | |
If _last_ row is all 0's returns None. | |
''' | |
j = deg_f | |
for i in range(0, M.cols): | |
if M[M.rows - 1, i] == 0: | |
j = j - 1 | |
else: | |
return j if j >= 0 else 0 | |
def final_touches(s2, r, deg_g): | |
""" | |
s2 is sylvester2, r is the row pointer in s2, | |
deg_g is the degree of the poly last inserted in s2. | |
After a gcd of degree > 0 has been found with Van Vleck's | |
method, and was inserted into s2, if its last term is not | |
in the last column of s2, then it is inserted as many | |
times as needed, rotated right by one each time, until | |
the condition is met. | |
""" | |
R = s2.row(r-1) | |
# find the first non zero term | |
for i in range(s2.cols): | |
if R[0,i] == 0: | |
continue | |
else: | |
break | |
# missing rows until last term is in last column | |
mr = s2.cols - (i + deg_g + 1) | |
# insert them by replacing the existing entries in the row | |
i = 0 | |
while mr != 0 and r + i < s2.rows : | |
s2[r + i, : ] = rotate_r(R, i + 1) | |
i += 1 | |
mr -= 1 | |
return s2 | |
def subresultants_vv(p, q, x, method = 0): | |
""" | |
p, q are polynomials in Z[x] (intended) or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the subresultant prs of p, q by triangularizing, | |
in Z[x] or in Q[x], all the smaller matrices encountered in the | |
process of triangularizing sylvester2, Sylvester's matrix of 1853; | |
see references 1 and 2 for Van Vleck's method. With each remainder, | |
sylvester2 gets updated and is prepared to be printed if requested. | |
If sylvester2 has small dimensions and you want to see the final, | |
triangularized matrix use this version with method=1; otherwise, | |
use either this version with method=0 (default) or the faster version, | |
subresultants_vv_2(p, q, x), where sylvester2 is used implicitly. | |
Sylvester's matrix sylvester1 is also used to compute one | |
subresultant per remainder; namely, that of the leading | |
coefficient, in order to obtain the correct sign and to | |
force the remainder coefficients to become subresultants. | |
If the subresultant prs is complete, then it coincides with the | |
Euclidean sequence of the polynomials p, q. | |
If the final, triangularized matrix s2 is printed, then: | |
(a) if deg(p) - deg(q) > 1 or deg( gcd(p, q) ) > 0, several | |
of the last rows in s2 will remain unprocessed; | |
(b) if deg(p) - deg(q) == 0, p will not appear in the final matrix. | |
References | |
========== | |
1. Akritas, A. G.: ``A new method for computing polynomial greatest | |
common divisors and polynomial remainder sequences.'' | |
Numerische MatheMatik 52, 119-127, 1988. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem | |
by Van Vleck Regarding Sturm Sequences.'' | |
Serdica Journal of Computing, 7, No 4, 101-134, 2013. | |
3. Akritas, A. G.:``Three New Methods for Computing Subresultant | |
Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
f, g = p, q | |
n = deg_f = degree(f, x) | |
m = deg_g = degree(g, x) | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
# initialize | |
s1 = sylvester(f, g, x, 1) | |
s2 = sylvester(f, g, x, 2) | |
sr_list = [f, g] | |
col_num = 2 * n # columns in s2 | |
# make two rows (row0, row1) of poly coefficients | |
row0 = Poly(f, x, domain = QQ).all_coeffs() | |
leng0 = len(row0) | |
for i in range(col_num - leng0): | |
row0.append(0) | |
row0 = Matrix([row0]) | |
row1 = Poly(g,x, domain = QQ).all_coeffs() | |
leng1 = len(row1) | |
for i in range(col_num - leng1): | |
row1.append(0) | |
row1 = Matrix([row1]) | |
# row pointer for deg_f - deg_g == 1; may be reset below | |
r = 2 | |
# modify first rows of s2 matrix depending on poly degrees | |
if deg_f - deg_g > 1: | |
r = 1 | |
# replacing the existing entries in the rows of s2, | |
# insert row0 (deg_f - deg_g - 1) times, rotated each time | |
for i in range(deg_f - deg_g - 1): | |
s2[r + i, : ] = rotate_r(row0, i + 1) | |
r = r + deg_f - deg_g - 1 | |
# insert row1 (deg_f - deg_g) times, rotated each time | |
for i in range(deg_f - deg_g): | |
s2[r + i, : ] = rotate_r(row1, r + i) | |
r = r + deg_f - deg_g | |
if deg_f - deg_g == 0: | |
r = 0 | |
# main loop | |
while deg_g > 0: | |
# create a small matrix M, and triangularize it; | |
M = create_ma(deg_f, deg_g, row1, row0, col_num) | |
# will need only the first and last rows of M | |
for i in range(deg_f - deg_g + 1): | |
M1 = pivot(M, i, i) | |
M = M1[:, :] | |
# treat last row of M as poly; find its degree | |
d = find_degree(M, deg_f) | |
if d is None: | |
break | |
exp_deg = deg_g - 1 | |
# evaluate one determinant & make coefficients subresultants | |
sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d) | |
poly = row2poly(M[M.rows - 1, :], d, x) | |
temp2 = LC(poly, x) | |
poly = simplify((poly / temp2) * sign_value) | |
# update s2 by inserting first row of M as needed | |
row0 = M[0, :] | |
for i in range(deg_g - d): | |
s2[r + i, :] = rotate_r(row0, r + i) | |
r = r + deg_g - d | |
# update s2 by inserting last row of M as needed | |
row1 = rotate_l(M[M.rows - 1, :], deg_f - d) | |
row1 = (row1 / temp2) * sign_value | |
for i in range(deg_g - d): | |
s2[r + i, :] = rotate_r(row1, r + i) | |
r = r + deg_g - d | |
# update degrees | |
deg_f, deg_g = deg_g, d | |
# append poly with subresultant coeffs | |
sr_list.append(poly) | |
# final touches to print the s2 matrix | |
if method != 0 and s2.rows > 2: | |
s2 = final_touches(s2, r, deg_g) | |
pprint(s2) | |
elif method != 0 and s2.rows == 2: | |
s2[1, :] = rotate_r(s2.row(1), 1) | |
pprint(s2) | |
return sr_list | |
def subresultants_vv_2(p, q, x): | |
""" | |
p, q are polynomials in Z[x] (intended) or Q[x]. It is assumed | |
that degree(p, x) >= degree(q, x). | |
Computes the subresultant prs of p, q by triangularizing, | |
in Z[x] or in Q[x], all the smaller matrices encountered in the | |
process of triangularizing sylvester2, Sylvester's matrix of 1853; | |
see references 1 and 2 for Van Vleck's method. | |
If the sylvester2 matrix has big dimensions use this version, | |
where sylvester2 is used implicitly. If you want to see the final, | |
triangularized matrix sylvester2, then use the first version, | |
subresultants_vv(p, q, x, 1). | |
sylvester1, Sylvester's matrix of 1840, is also used to compute | |
one subresultant per remainder; namely, that of the leading | |
coefficient, in order to obtain the correct sign and to | |
``force'' the remainder coefficients to become subresultants. | |
If the subresultant prs is complete, then it coincides with the | |
Euclidean sequence of the polynomials p, q. | |
References | |
========== | |
1. Akritas, A. G.: ``A new method for computing polynomial greatest | |
common divisors and polynomial remainder sequences.'' | |
Numerische MatheMatik 52, 119-127, 1988. | |
2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem | |
by Van Vleck Regarding Sturm Sequences.'' | |
Serdica Journal of Computing, 7, No 4, 101-134, 2013. | |
3. Akritas, A. G.:``Three New Methods for Computing Subresultant | |
Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26. | |
""" | |
# make sure neither p nor q is 0 | |
if p == 0 or q == 0: | |
return [p, q] | |
# make sure proper degrees | |
f, g = p, q | |
n = deg_f = degree(f, x) | |
m = deg_g = degree(g, x) | |
if n == 0 and m == 0: | |
return [f, g] | |
if n < m: | |
n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f | |
if n > 0 and m == 0: | |
return [f, g] | |
# initialize | |
s1 = sylvester(f, g, x, 1) | |
sr_list = [f, g] # subresultant list | |
col_num = 2 * n # columns in sylvester2 | |
# make two rows (row0, row1) of poly coefficients | |
row0 = Poly(f, x, domain = QQ).all_coeffs() | |
leng0 = len(row0) | |
for i in range(col_num - leng0): | |
row0.append(0) | |
row0 = Matrix([row0]) | |
row1 = Poly(g,x, domain = QQ).all_coeffs() | |
leng1 = len(row1) | |
for i in range(col_num - leng1): | |
row1.append(0) | |
row1 = Matrix([row1]) | |
# main loop | |
while deg_g > 0: | |
# create a small matrix M, and triangularize it | |
M = create_ma(deg_f, deg_g, row1, row0, col_num) | |
for i in range(deg_f - deg_g + 1): | |
M1 = pivot(M, i, i) | |
M = M1[:, :] | |
# treat last row of M as poly; find its degree | |
d = find_degree(M, deg_f) | |
if d is None: | |
return sr_list | |
exp_deg = deg_g - 1 | |
# evaluate one determinant & make coefficients subresultants | |
sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d) | |
poly = row2poly(M[M.rows - 1, :], d, x) | |
poly = simplify((poly / LC(poly, x)) * sign_value) | |
# append poly with subresultant coeffs | |
sr_list.append(poly) | |
# update degrees and rows | |
deg_f, deg_g = deg_g, d | |
row0 = row1 | |
row1 = Poly(poly, x, domain = QQ).all_coeffs() | |
leng1 = len(row1) | |
for i in range(col_num - leng1): | |
row1.append(0) | |
row1 = Matrix([row1]) | |
return sr_list | |