multivariate_resultants.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. """
  2. This module contains functions for two multivariate resultants. These
  3. are:
  4. - Dixon's resultant.
  5. - Macaulay's resultant.
  6. Multivariate resultants are used to identify whether a multivariate
  7. system has common roots. That is when the resultant is equal to zero.
  8. """
  9. from math import prod
  10. from sympy.core.mul import Mul
  11. from sympy.matrices.dense import (Matrix, diag)
  12. from sympy.polys.polytools import (Poly, degree_list, rem)
  13. from sympy.simplify.simplify import simplify
  14. from sympy.tensor.indexed import IndexedBase
  15. from sympy.polys.monomials import itermonomials, monomial_deg
  16. from sympy.polys.orderings import monomial_key
  17. from sympy.polys.polytools import poly_from_expr, total_degree
  18. from sympy.functions.combinatorial.factorials import binomial
  19. from itertools import combinations_with_replacement
  20. from sympy.utilities.exceptions import sympy_deprecation_warning
  21. class DixonResultant():
  22. """
  23. A class for retrieving the Dixon's resultant of a multivariate
  24. system.
  25. Examples
  26. ========
  27. >>> from sympy import symbols
  28. >>> from sympy.polys.multivariate_resultants import DixonResultant
  29. >>> x, y = symbols('x, y')
  30. >>> p = x + y
  31. >>> q = x ** 2 + y ** 3
  32. >>> h = x ** 2 + y
  33. >>> dixon = DixonResultant(variables=[x, y], polynomials=[p, q, h])
  34. >>> poly = dixon.get_dixon_polynomial()
  35. >>> matrix = dixon.get_dixon_matrix(polynomial=poly)
  36. >>> matrix
  37. Matrix([
  38. [ 0, 0, -1, 0, -1],
  39. [ 0, -1, 0, -1, 0],
  40. [-1, 0, 1, 0, 0],
  41. [ 0, -1, 0, 0, 1],
  42. [-1, 0, 0, 1, 0]])
  43. >>> matrix.det()
  44. 0
  45. See Also
  46. ========
  47. Notebook in examples: sympy/example/notebooks.
  48. References
  49. ==========
  50. .. [1] [Kapur1994]_
  51. .. [2] [Palancz08]_
  52. """
  53. def __init__(self, polynomials, variables):
  54. """
  55. A class that takes two lists, a list of polynomials and list of
  56. variables. Returns the Dixon matrix of the multivariate system.
  57. Parameters
  58. ----------
  59. polynomials : list of polynomials
  60. A list of m n-degree polynomials
  61. variables: list
  62. A list of all n variables
  63. """
  64. self.polynomials = polynomials
  65. self.variables = variables
  66. self.n = len(self.variables)
  67. self.m = len(self.polynomials)
  68. a = IndexedBase("alpha")
  69. # A list of n alpha variables (the replacing variables)
  70. self.dummy_variables = [a[i] for i in range(self.n)]
  71. # A list of the d_max of each variable.
  72. self._max_degrees = [max(degree_list(poly)[i] for poly in self.polynomials)
  73. for i in range(self.n)]
  74. @property
  75. def max_degrees(self):
  76. sympy_deprecation_warning(
  77. """
  78. The max_degrees property of DixonResultant is deprecated.
  79. """,
  80. deprecated_since_version="1.5",
  81. active_deprecations_target="deprecated-dixonresultant-properties",
  82. )
  83. return self._max_degrees
  84. def get_dixon_polynomial(self):
  85. r"""
  86. Returns
  87. =======
  88. dixon_polynomial: polynomial
  89. Dixon's polynomial is calculated as:
  90. delta = Delta(A) / ((x_1 - a_1) ... (x_n - a_n)) where,
  91. A = |p_1(x_1,... x_n), ..., p_n(x_1,... x_n)|
  92. |p_1(a_1,... x_n), ..., p_n(a_1,... x_n)|
  93. |... , ..., ...|
  94. |p_1(a_1,... a_n), ..., p_n(a_1,... a_n)|
  95. """
  96. if self.m != (self.n + 1):
  97. raise ValueError('Method invalid for given combination.')
  98. # First row
  99. rows = [self.polynomials]
  100. temp = list(self.variables)
  101. for idx in range(self.n):
  102. temp[idx] = self.dummy_variables[idx]
  103. substitution = {var: t for var, t in zip(self.variables, temp)}
  104. rows.append([f.subs(substitution) for f in self.polynomials])
  105. A = Matrix(rows)
  106. terms = zip(self.variables, self.dummy_variables)
  107. product_of_differences = Mul(*[a - b for a, b in terms])
  108. dixon_polynomial = (A.det() / product_of_differences).factor()
  109. return poly_from_expr(dixon_polynomial, self.dummy_variables)[0]
  110. def get_upper_degree(self):
  111. sympy_deprecation_warning(
  112. """
  113. The get_upper_degree() method of DixonResultant is deprecated. Use
  114. get_max_degrees() instead.
  115. """,
  116. deprecated_since_version="1.5",
  117. active_deprecations_target="deprecated-dixonresultant-properties"
  118. )
  119. list_of_products = [self.variables[i] ** self._max_degrees[i]
  120. for i in range(self.n)]
  121. product = prod(list_of_products)
  122. product = Poly(product).monoms()
  123. return monomial_deg(*product)
  124. def get_max_degrees(self, polynomial):
  125. r"""
  126. Returns a list of the maximum degree of each variable appearing
  127. in the coefficients of the Dixon polynomial. The coefficients are
  128. viewed as polys in $x_1, x_2, \dots, x_n$.
  129. """
  130. deg_lists = [degree_list(Poly(poly, self.variables))
  131. for poly in polynomial.coeffs()]
  132. max_degrees = [max(degs) for degs in zip(*deg_lists)]
  133. return max_degrees
  134. def get_dixon_matrix(self, polynomial):
  135. r"""
  136. Construct the Dixon matrix from the coefficients of polynomial
  137. \alpha. Each coefficient is viewed as a polynomial of x_1, ...,
  138. x_n.
  139. """
  140. max_degrees = self.get_max_degrees(polynomial)
  141. # list of column headers of the Dixon matrix.
  142. monomials = itermonomials(self.variables, max_degrees)
  143. monomials = sorted(monomials, reverse=True,
  144. key=monomial_key('lex', self.variables))
  145. dixon_matrix = Matrix([[Poly(c, *self.variables).coeff_monomial(m)
  146. for m in monomials]
  147. for c in polynomial.coeffs()])
  148. # remove columns if needed
  149. if dixon_matrix.shape[0] != dixon_matrix.shape[1]:
  150. keep = [column for column in range(dixon_matrix.shape[-1])
  151. if any(element != 0 for element
  152. in dixon_matrix[:, column])]
  153. dixon_matrix = dixon_matrix[:, keep]
  154. return dixon_matrix
  155. def KSY_precondition(self, matrix):
  156. """
  157. Test for the validity of the Kapur-Saxena-Yang precondition.
  158. The precondition requires that the column corresponding to the
  159. monomial 1 = x_1 ^ 0 * x_2 ^ 0 * ... * x_n ^ 0 is not a linear
  160. combination of the remaining ones. In SymPy notation this is
  161. the last column. For the precondition to hold the last non-zero
  162. row of the rref matrix should be of the form [0, 0, ..., 1].
  163. """
  164. if matrix.is_zero_matrix:
  165. return False
  166. m, n = matrix.shape
  167. # simplify the matrix and keep only its non-zero rows
  168. matrix = simplify(matrix.rref()[0])
  169. rows = [i for i in range(m) if any(matrix[i, j] != 0 for j in range(n))]
  170. matrix = matrix[rows,:]
  171. condition = Matrix([[0]*(n-1) + [1]])
  172. if matrix[-1,:] == condition:
  173. return True
  174. else:
  175. return False
  176. def delete_zero_rows_and_columns(self, matrix):
  177. """Remove the zero rows and columns of the matrix."""
  178. rows = [
  179. i for i in range(matrix.rows) if not matrix.row(i).is_zero_matrix]
  180. cols = [
  181. j for j in range(matrix.cols) if not matrix.col(j).is_zero_matrix]
  182. return matrix[rows, cols]
  183. def product_leading_entries(self, matrix):
  184. """Calculate the product of the leading entries of the matrix."""
  185. res = 1
  186. for row in range(matrix.rows):
  187. for el in matrix.row(row):
  188. if el != 0:
  189. res = res * el
  190. break
  191. return res
  192. def get_KSY_Dixon_resultant(self, matrix):
  193. """Calculate the Kapur-Saxena-Yang approach to the Dixon Resultant."""
  194. matrix = self.delete_zero_rows_and_columns(matrix)
  195. _, U, _ = matrix.LUdecomposition()
  196. matrix = self.delete_zero_rows_and_columns(simplify(U))
  197. return self.product_leading_entries(matrix)
  198. class MacaulayResultant():
  199. """
  200. A class for calculating the Macaulay resultant. Note that the
  201. polynomials must be homogenized and their coefficients must be
  202. given as symbols.
  203. Examples
  204. ========
  205. >>> from sympy import symbols
  206. >>> from sympy.polys.multivariate_resultants import MacaulayResultant
  207. >>> x, y, z = symbols('x, y, z')
  208. >>> a_0, a_1, a_2 = symbols('a_0, a_1, a_2')
  209. >>> b_0, b_1, b_2 = symbols('b_0, b_1, b_2')
  210. >>> c_0, c_1, c_2,c_3, c_4 = symbols('c_0, c_1, c_2, c_3, c_4')
  211. >>> f = a_0 * y - a_1 * x + a_2 * z
  212. >>> g = b_1 * x ** 2 + b_0 * y ** 2 - b_2 * z ** 2
  213. >>> h = c_0 * y * z ** 2 - c_1 * x ** 3 + c_2 * x ** 2 * z - c_3 * x * z ** 2 + c_4 * z ** 3
  214. >>> mac = MacaulayResultant(polynomials=[f, g, h], variables=[x, y, z])
  215. >>> mac.monomial_set
  216. [x**4, x**3*y, x**3*z, x**2*y**2, x**2*y*z, x**2*z**2, x*y**3,
  217. 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]
  218. >>> matrix = mac.get_matrix()
  219. >>> submatrix = mac.get_submatrix(matrix)
  220. >>> submatrix
  221. Matrix([
  222. [-a_1, a_0, a_2, 0],
  223. [ 0, -a_1, 0, 0],
  224. [ 0, 0, -a_1, 0],
  225. [ 0, 0, 0, -a_1]])
  226. See Also
  227. ========
  228. Notebook in examples: sympy/example/notebooks.
  229. References
  230. ==========
  231. .. [1] [Bruce97]_
  232. .. [2] [Stiller96]_
  233. """
  234. def __init__(self, polynomials, variables):
  235. """
  236. Parameters
  237. ==========
  238. variables: list
  239. A list of all n variables
  240. polynomials : list of SymPy polynomials
  241. A list of m n-degree polynomials
  242. """
  243. self.polynomials = polynomials
  244. self.variables = variables
  245. self.n = len(variables)
  246. # A list of the d_max of each polynomial.
  247. self.degrees = [total_degree(poly, *self.variables) for poly
  248. in self.polynomials]
  249. self.degree_m = self._get_degree_m()
  250. self.monomials_size = self.get_size()
  251. # The set T of all possible monomials of degree degree_m
  252. self.monomial_set = self.get_monomials_of_certain_degree(self.degree_m)
  253. def _get_degree_m(self):
  254. r"""
  255. Returns
  256. =======
  257. degree_m: int
  258. The degree_m is calculated as 1 + \sum_1 ^ n (d_i - 1),
  259. where d_i is the degree of the i polynomial
  260. """
  261. return 1 + sum(d - 1 for d in self.degrees)
  262. def get_size(self):
  263. r"""
  264. Returns
  265. =======
  266. size: int
  267. The size of set T. Set T is the set of all possible
  268. monomials of the n variables for degree equal to the
  269. degree_m
  270. """
  271. return binomial(self.degree_m + self.n - 1, self.n - 1)
  272. def get_monomials_of_certain_degree(self, degree):
  273. """
  274. Returns
  275. =======
  276. monomials: list
  277. A list of monomials of a certain degree.
  278. """
  279. monomials = [Mul(*monomial) for monomial
  280. in combinations_with_replacement(self.variables,
  281. degree)]
  282. return sorted(monomials, reverse=True,
  283. key=monomial_key('lex', self.variables))
  284. def get_row_coefficients(self):
  285. """
  286. Returns
  287. =======
  288. row_coefficients: list
  289. The row coefficients of Macaulay's matrix
  290. """
  291. row_coefficients = []
  292. divisible = []
  293. for i in range(self.n):
  294. if i == 0:
  295. degree = self.degree_m - self.degrees[i]
  296. monomial = self.get_monomials_of_certain_degree(degree)
  297. row_coefficients.append(monomial)
  298. else:
  299. divisible.append(self.variables[i - 1] **
  300. self.degrees[i - 1])
  301. degree = self.degree_m - self.degrees[i]
  302. poss_rows = self.get_monomials_of_certain_degree(degree)
  303. for div in divisible:
  304. for p in poss_rows:
  305. if rem(p, div) == 0:
  306. poss_rows = [item for item in poss_rows
  307. if item != p]
  308. row_coefficients.append(poss_rows)
  309. return row_coefficients
  310. def get_matrix(self):
  311. """
  312. Returns
  313. =======
  314. macaulay_matrix: Matrix
  315. The Macaulay numerator matrix
  316. """
  317. rows = []
  318. row_coefficients = self.get_row_coefficients()
  319. for i in range(self.n):
  320. for multiplier in row_coefficients[i]:
  321. coefficients = []
  322. poly = Poly(self.polynomials[i] * multiplier,
  323. *self.variables)
  324. for mono in self.monomial_set:
  325. coefficients.append(poly.coeff_monomial(mono))
  326. rows.append(coefficients)
  327. macaulay_matrix = Matrix(rows)
  328. return macaulay_matrix
  329. def get_reduced_nonreduced(self):
  330. r"""
  331. Returns
  332. =======
  333. reduced: list
  334. A list of the reduced monomials
  335. non_reduced: list
  336. A list of the monomials that are not reduced
  337. Definition
  338. ==========
  339. A polynomial is said to be reduced in x_i, if its degree (the
  340. maximum degree of its monomials) in x_i is less than d_i. A
  341. polynomial that is reduced in all variables but one is said
  342. simply to be reduced.
  343. """
  344. divisible = []
  345. for m in self.monomial_set:
  346. temp = []
  347. for i, v in enumerate(self.variables):
  348. temp.append(bool(total_degree(m, v) >= self.degrees[i]))
  349. divisible.append(temp)
  350. reduced = [i for i, r in enumerate(divisible)
  351. if sum(r) < self.n - 1]
  352. non_reduced = [i for i, r in enumerate(divisible)
  353. if sum(r) >= self.n -1]
  354. return reduced, non_reduced
  355. def get_submatrix(self, matrix):
  356. r"""
  357. Returns
  358. =======
  359. macaulay_submatrix: Matrix
  360. The Macaulay denominator matrix. Columns that are non reduced are kept.
  361. The row which contains one of the a_{i}s is dropped. a_{i}s
  362. are the coefficients of x_i ^ {d_i}.
  363. """
  364. reduced, non_reduced = self.get_reduced_nonreduced()
  365. # if reduced == [], then det(matrix) should be 1
  366. if reduced == []:
  367. return diag([1])
  368. # reduced != []
  369. reduction_set = [v ** self.degrees[i] for i, v
  370. in enumerate(self.variables)]
  371. ais = [self.polynomials[i].coeff(reduction_set[i])
  372. for i in range(self.n)]
  373. reduced_matrix = matrix[:, reduced]
  374. keep = []
  375. for row in range(reduced_matrix.rows):
  376. check = [ai in reduced_matrix[row, :] for ai in ais]
  377. if True not in check:
  378. keep.append(row)
  379. return matrix[keep, non_reduced]