You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

474 lines
15 KiB

5 months ago
"""
This module contains functions for two multivariate resultants. These
are:
- Dixon's resultant.
- Macaulay's resultant.
Multivariate resultants are used to identify whether a multivariate
system has common roots. That is when the resultant is equal to zero.
"""
from math import prod
from sympy.core.mul import Mul
from sympy.matrices.dense import (Matrix, diag)
from sympy.polys.polytools import (Poly, degree_list, rem)
from sympy.simplify.simplify import simplify
from sympy.tensor.indexed import IndexedBase
from sympy.polys.monomials import itermonomials, monomial_deg
from sympy.polys.orderings import monomial_key
from sympy.polys.polytools import poly_from_expr, total_degree
from sympy.functions.combinatorial.factorials import binomial
from itertools import combinations_with_replacement
from sympy.utilities.exceptions import sympy_deprecation_warning
class DixonResultant():
"""
A class for retrieving the Dixon's resultant of a multivariate
system.
Examples
========
>>> from sympy import symbols
>>> from sympy.polys.multivariate_resultants import DixonResultant
>>> x, y = symbols('x, y')
>>> p = x + y
>>> q = x ** 2 + y ** 3
>>> h = x ** 2 + y
>>> dixon = DixonResultant(variables=[x, y], polynomials=[p, q, h])
>>> poly = dixon.get_dixon_polynomial()
>>> matrix = dixon.get_dixon_matrix(polynomial=poly)
>>> matrix
Matrix([
[ 0, 0, -1, 0, -1],
[ 0, -1, 0, -1, 0],
[-1, 0, 1, 0, 0],
[ 0, -1, 0, 0, 1],
[-1, 0, 0, 1, 0]])
>>> matrix.det()
0
See Also
========
Notebook in examples: sympy/example/notebooks.
References
==========
.. [1] [Kapur1994]_
.. [2] [Palancz08]_
"""
def __init__(self, polynomials, variables):
"""
A class that takes two lists, a list of polynomials and list of
variables. Returns the Dixon matrix of the multivariate system.
Parameters
----------
polynomials : list of polynomials
A list of m n-degree polynomials
variables: list
A list of all n variables
"""
self.polynomials = polynomials
self.variables = variables
self.n = len(self.variables)
self.m = len(self.polynomials)
a = IndexedBase("alpha")
# A list of n alpha variables (the replacing variables)
self.dummy_variables = [a[i] for i in range(self.n)]
# A list of the d_max of each variable.
self._max_degrees = [max(degree_list(poly)[i] for poly in self.polynomials)
for i in range(self.n)]
@property
def max_degrees(self):
sympy_deprecation_warning(
"""
The max_degrees property of DixonResultant is deprecated.
""",
deprecated_since_version="1.5",
active_deprecations_target="deprecated-dixonresultant-properties",
)
return self._max_degrees
def get_dixon_polynomial(self):
r"""
Returns
=======
dixon_polynomial: polynomial
Dixon's polynomial is calculated as:
delta = Delta(A) / ((x_1 - a_1) ... (x_n - a_n)) where,
A = |p_1(x_1,... x_n), ..., p_n(x_1,... x_n)|
|p_1(a_1,... x_n), ..., p_n(a_1,... x_n)|
|... , ..., ...|
|p_1(a_1,... a_n), ..., p_n(a_1,... a_n)|
"""
if self.m != (self.n + 1):
raise ValueError('Method invalid for given combination.')
# First row
rows = [self.polynomials]
temp = list(self.variables)
for idx in range(self.n):
temp[idx] = self.dummy_variables[idx]
substitution = {var: t for var, t in zip(self.variables, temp)}
rows.append([f.subs(substitution) for f in self.polynomials])
A = Matrix(rows)
terms = zip(self.variables, self.dummy_variables)
product_of_differences = Mul(*[a - b for a, b in terms])
dixon_polynomial = (A.det() / product_of_differences).factor()
return poly_from_expr(dixon_polynomial, self.dummy_variables)[0]
def get_upper_degree(self):
sympy_deprecation_warning(
"""
The get_upper_degree() method of DixonResultant is deprecated. Use
get_max_degrees() instead.
""",
deprecated_since_version="1.5",
active_deprecations_target="deprecated-dixonresultant-properties"
)
list_of_products = [self.variables[i] ** self._max_degrees[i]
for i in range(self.n)]
product = prod(list_of_products)
product = Poly(product).monoms()
return monomial_deg(*product)
def get_max_degrees(self, polynomial):
r"""
Returns a list of the maximum degree of each variable appearing
in the coefficients of the Dixon polynomial. The coefficients are
viewed as polys in $x_1, x_2, \dots, x_n$.
"""
deg_lists = [degree_list(Poly(poly, self.variables))
for poly in polynomial.coeffs()]
max_degrees = [max(degs) for degs in zip(*deg_lists)]
return max_degrees
def get_dixon_matrix(self, polynomial):
r"""
Construct the Dixon matrix from the coefficients of polynomial
\alpha. Each coefficient is viewed as a polynomial of x_1, ...,
x_n.
"""
max_degrees = self.get_max_degrees(polynomial)
# list of column headers of the Dixon matrix.
monomials = itermonomials(self.variables, max_degrees)
monomials = sorted(monomials, reverse=True,
key=monomial_key('lex', self.variables))
dixon_matrix = Matrix([[Poly(c, *self.variables).coeff_monomial(m)
for m in monomials]
for c in polynomial.coeffs()])
# remove columns if needed
if dixon_matrix.shape[0] != dixon_matrix.shape[1]:
keep = [column for column in range(dixon_matrix.shape[-1])
if any(element != 0 for element
in dixon_matrix[:, column])]
dixon_matrix = dixon_matrix[:, keep]
return dixon_matrix
def KSY_precondition(self, matrix):
"""
Test for the validity of the Kapur-Saxena-Yang precondition.
The precondition requires that the column corresponding to the
monomial 1 = x_1 ^ 0 * x_2 ^ 0 * ... * x_n ^ 0 is not a linear
combination of the remaining ones. In SymPy notation this is
the last column. For the precondition to hold the last non-zero
row of the rref matrix should be of the form [0, 0, ..., 1].
"""
if matrix.is_zero_matrix:
return False
m, n = matrix.shape
# simplify the matrix and keep only its non-zero rows
matrix = simplify(matrix.rref()[0])
rows = [i for i in range(m) if any(matrix[i, j] != 0 for j in range(n))]
matrix = matrix[rows,:]
condition = Matrix([[0]*(n-1) + [1]])
if matrix[-1,:] == condition:
return True
else:
return False
def delete_zero_rows_and_columns(self, matrix):
"""Remove the zero rows and columns of the matrix."""
rows = [
i for i in range(matrix.rows) if not matrix.row(i).is_zero_matrix]
cols = [
j for j in range(matrix.cols) if not matrix.col(j).is_zero_matrix]
return matrix[rows, cols]
def product_leading_entries(self, matrix):
"""Calculate the product of the leading entries of the matrix."""
res = 1
for row in range(matrix.rows):
for el in matrix.row(row):
if el != 0:
res = res * el
break
return res
def get_KSY_Dixon_resultant(self, matrix):
"""Calculate the Kapur-Saxena-Yang approach to the Dixon Resultant."""
matrix = self.delete_zero_rows_and_columns(matrix)
_, U, _ = matrix.LUdecomposition()
matrix = self.delete_zero_rows_and_columns(simplify(U))
return self.product_leading_entries(matrix)
class MacaulayResultant():
"""
A class for calculating the Macaulay resultant. Note that the
polynomials must be homogenized and their coefficients must be
given as symbols.
Examples
========
>>> from sympy import symbols
>>> from sympy.polys.multivariate_resultants import MacaulayResultant
>>> x, y, z = symbols('x, y, z')
>>> a_0, a_1, a_2 = symbols('a_0, a_1, a_2')
>>> b_0, b_1, b_2 = symbols('b_0, b_1, b_2')
>>> c_0, c_1, c_2,c_3, c_4 = symbols('c_0, c_1, c_2, c_3, c_4')
>>> f = a_0 * y - a_1 * x + a_2 * z
>>> g = b_1 * x ** 2 + b_0 * y ** 2 - b_2 * z ** 2
>>> h = c_0 * y * z ** 2 - c_1 * x ** 3 + c_2 * x ** 2 * z - c_3 * x * z ** 2 + c_4 * z ** 3
>>> mac = MacaulayResultant(polynomials=[f, g, h], variables=[x, y, z])
>>> mac.monomial_set
[x**4, x**3*y, x**3*z, x**2*y**2, x**2*y*z, x**2*z**2, x*y**3,
x*y**2*z, x*y*z**2, x*z**3, y**4, y**3*z, y**2*z**2, y*z**3, z**4]
>>> matrix = mac.get_matrix()
>>> submatrix = mac.get_submatrix(matrix)
>>> submatrix
Matrix([
[-a_1, a_0, a_2, 0],
[ 0, -a_1, 0, 0],
[ 0, 0, -a_1, 0],
[ 0, 0, 0, -a_1]])
See Also
========
Notebook in examples: sympy/example/notebooks.
References
==========
.. [1] [Bruce97]_
.. [2] [Stiller96]_
"""
def __init__(self, polynomials, variables):
"""
Parameters
==========
variables: list
A list of all n variables
polynomials : list of SymPy polynomials
A list of m n-degree polynomials
"""
self.polynomials = polynomials
self.variables = variables
self.n = len(variables)
# A list of the d_max of each polynomial.
self.degrees = [total_degree(poly, *self.variables) for poly
in self.polynomials]
self.degree_m = self._get_degree_m()
self.monomials_size = self.get_size()
# The set T of all possible monomials of degree degree_m
self.monomial_set = self.get_monomials_of_certain_degree(self.degree_m)
def _get_degree_m(self):
r"""
Returns
=======
degree_m: int
The degree_m is calculated as 1 + \sum_1 ^ n (d_i - 1),
where d_i is the degree of the i polynomial
"""
return 1 + sum(d - 1 for d in self.degrees)
def get_size(self):
r"""
Returns
=======
size: int
The size of set T. Set T is the set of all possible
monomials of the n variables for degree equal to the
degree_m
"""
return binomial(self.degree_m + self.n - 1, self.n - 1)
def get_monomials_of_certain_degree(self, degree):
"""
Returns
=======
monomials: list
A list of monomials of a certain degree.
"""
monomials = [Mul(*monomial) for monomial
in combinations_with_replacement(self.variables,
degree)]
return sorted(monomials, reverse=True,
key=monomial_key('lex', self.variables))
def get_row_coefficients(self):
"""
Returns
=======
row_coefficients: list
The row coefficients of Macaulay's matrix
"""
row_coefficients = []
divisible = []
for i in range(self.n):
if i == 0:
degree = self.degree_m - self.degrees[i]
monomial = self.get_monomials_of_certain_degree(degree)
row_coefficients.append(monomial)
else:
divisible.append(self.variables[i - 1] **
self.degrees[i - 1])
degree = self.degree_m - self.degrees[i]
poss_rows = self.get_monomials_of_certain_degree(degree)
for div in divisible:
for p in poss_rows:
if rem(p, div) == 0:
poss_rows = [item for item in poss_rows
if item != p]
row_coefficients.append(poss_rows)
return row_coefficients
def get_matrix(self):
"""
Returns
=======
macaulay_matrix: Matrix
The Macaulay numerator matrix
"""
rows = []
row_coefficients = self.get_row_coefficients()
for i in range(self.n):
for multiplier in row_coefficients[i]:
coefficients = []
poly = Poly(self.polynomials[i] * multiplier,
*self.variables)
for mono in self.monomial_set:
coefficients.append(poly.coeff_monomial(mono))
rows.append(coefficients)
macaulay_matrix = Matrix(rows)
return macaulay_matrix
def get_reduced_nonreduced(self):
r"""
Returns
=======
reduced: list
A list of the reduced monomials
non_reduced: list
A list of the monomials that are not reduced
Definition
==========
A polynomial is said to be reduced in x_i, if its degree (the
maximum degree of its monomials) in x_i is less than d_i. A
polynomial that is reduced in all variables but one is said
simply to be reduced.
"""
divisible = []
for m in self.monomial_set:
temp = []
for i, v in enumerate(self.variables):
temp.append(bool(total_degree(m, v) >= self.degrees[i]))
divisible.append(temp)
reduced = [i for i, r in enumerate(divisible)
if sum(r) < self.n - 1]
non_reduced = [i for i, r in enumerate(divisible)
if sum(r) >= self.n -1]
return reduced, non_reduced
def get_submatrix(self, matrix):
r"""
Returns
=======
macaulay_submatrix: Matrix
The Macaulay denominator matrix. Columns that are non reduced are kept.
The row which contains one of the a_{i}s is dropped. a_{i}s
are the coefficients of x_i ^ {d_i}.
"""
reduced, non_reduced = self.get_reduced_nonreduced()
# if reduced == [], then det(matrix) should be 1
if reduced == []:
return diag([1])
# reduced != []
reduction_set = [v ** self.degrees[i] for i, v
in enumerate(self.variables)]
ais = [self.polynomials[i].coeff(reduction_set[i])
for i in range(self.n)]
reduced_matrix = matrix[:, reduced]
keep = []
for row in range(reduced_matrix.rows):
check = [ai in reduced_matrix[row, :] for ai in ais]
if True not in check:
keep.append(row)
return matrix[keep, non_reduced]