multivariate_resultants.py 15 KB

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