orderings.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. """Definitions of monomial orderings. """
  2. from typing import Optional
  3. __all__ = ["lex", "grlex", "grevlex", "ilex", "igrlex", "igrevlex"]
  4. from sympy.core import Symbol
  5. from sympy.utilities.iterables import iterable
  6. class MonomialOrder:
  7. """Base class for monomial orderings. """
  8. alias = None # type: Optional[str]
  9. is_global = None # type: Optional[bool]
  10. is_default = False
  11. def __repr__(self):
  12. return self.__class__.__name__ + "()"
  13. def __str__(self):
  14. return self.alias
  15. def __call__(self, monomial):
  16. raise NotImplementedError
  17. def __eq__(self, other):
  18. return self.__class__ == other.__class__
  19. def __hash__(self):
  20. return hash(self.__class__)
  21. def __ne__(self, other):
  22. return not (self == other)
  23. class LexOrder(MonomialOrder):
  24. """Lexicographic order of monomials. """
  25. alias = 'lex'
  26. is_global = True
  27. is_default = True
  28. def __call__(self, monomial):
  29. return monomial
  30. class GradedLexOrder(MonomialOrder):
  31. """Graded lexicographic order of monomials. """
  32. alias = 'grlex'
  33. is_global = True
  34. def __call__(self, monomial):
  35. return (sum(monomial), monomial)
  36. class ReversedGradedLexOrder(MonomialOrder):
  37. """Reversed graded lexicographic order of monomials. """
  38. alias = 'grevlex'
  39. is_global = True
  40. def __call__(self, monomial):
  41. return (sum(monomial), tuple(reversed([-m for m in monomial])))
  42. class ProductOrder(MonomialOrder):
  43. """
  44. A product order built from other monomial orders.
  45. Given (not necessarily total) orders O1, O2, ..., On, their product order
  46. P is defined as M1 > M2 iff there exists i such that O1(M1) = O2(M2),
  47. ..., Oi(M1) = Oi(M2), O{i+1}(M1) > O{i+1}(M2).
  48. Product orders are typically built from monomial orders on different sets
  49. of variables.
  50. ProductOrder is constructed by passing a list of pairs
  51. [(O1, L1), (O2, L2), ...] where Oi are MonomialOrders and Li are callables.
  52. Upon comparison, the Li are passed the total monomial, and should filter
  53. out the part of the monomial to pass to Oi.
  54. Examples
  55. ========
  56. We can use a lexicographic order on x_1, x_2 and also on
  57. y_1, y_2, y_3, and their product on {x_i, y_i} as follows:
  58. >>> from sympy.polys.orderings import lex, grlex, ProductOrder
  59. >>> P = ProductOrder(
  60. ... (lex, lambda m: m[:2]), # lex order on x_1 and x_2 of monomial
  61. ... (grlex, lambda m: m[2:]) # grlex on y_1, y_2, y_3
  62. ... )
  63. >>> P((2, 1, 1, 0, 0)) > P((1, 10, 0, 2, 0))
  64. True
  65. Here the exponent `2` of `x_1` in the first monomial
  66. (`x_1^2 x_2 y_1`) is bigger than the exponent `1` of `x_1` in the
  67. second monomial (`x_1 x_2^10 y_2^2`), so the first monomial is greater
  68. in the product ordering.
  69. >>> P((2, 1, 1, 0, 0)) < P((2, 1, 0, 2, 0))
  70. True
  71. Here the exponents of `x_1` and `x_2` agree, so the grlex order on
  72. `y_1, y_2, y_3` is used to decide the ordering. In this case the monomial
  73. `y_2^2` is ordered larger than `y_1`, since for the grlex order the degree
  74. of the monomial is most important.
  75. """
  76. def __init__(self, *args):
  77. self.args = args
  78. def __call__(self, monomial):
  79. return tuple(O(lamda(monomial)) for (O, lamda) in self.args)
  80. def __repr__(self):
  81. contents = [repr(x[0]) for x in self.args]
  82. return self.__class__.__name__ + '(' + ", ".join(contents) + ')'
  83. def __str__(self):
  84. contents = [str(x[0]) for x in self.args]
  85. return self.__class__.__name__ + '(' + ", ".join(contents) + ')'
  86. def __eq__(self, other):
  87. if not isinstance(other, ProductOrder):
  88. return False
  89. return self.args == other.args
  90. def __hash__(self):
  91. return hash((self.__class__, self.args))
  92. @property
  93. def is_global(self):
  94. if all(o.is_global is True for o, _ in self.args):
  95. return True
  96. if all(o.is_global is False for o, _ in self.args):
  97. return False
  98. return None
  99. class InverseOrder(MonomialOrder):
  100. """
  101. The "inverse" of another monomial order.
  102. If O is any monomial order, we can construct another monomial order iO
  103. such that `A >_{iO} B` if and only if `B >_O A`. This is useful for
  104. constructing local orders.
  105. Note that many algorithms only work with *global* orders.
  106. For example, in the inverse lexicographic order on a single variable `x`,
  107. high powers of `x` count as small:
  108. >>> from sympy.polys.orderings import lex, InverseOrder
  109. >>> ilex = InverseOrder(lex)
  110. >>> ilex((5,)) < ilex((0,))
  111. True
  112. """
  113. def __init__(self, O):
  114. self.O = O
  115. def __str__(self):
  116. return "i" + str(self.O)
  117. def __call__(self, monomial):
  118. def inv(l):
  119. if iterable(l):
  120. return tuple(inv(x) for x in l)
  121. return -l
  122. return inv(self.O(monomial))
  123. @property
  124. def is_global(self):
  125. if self.O.is_global is True:
  126. return False
  127. if self.O.is_global is False:
  128. return True
  129. return None
  130. def __eq__(self, other):
  131. return isinstance(other, InverseOrder) and other.O == self.O
  132. def __hash__(self):
  133. return hash((self.__class__, self.O))
  134. lex = LexOrder()
  135. grlex = GradedLexOrder()
  136. grevlex = ReversedGradedLexOrder()
  137. ilex = InverseOrder(lex)
  138. igrlex = InverseOrder(grlex)
  139. igrevlex = InverseOrder(grevlex)
  140. _monomial_key = {
  141. 'lex': lex,
  142. 'grlex': grlex,
  143. 'grevlex': grevlex,
  144. 'ilex': ilex,
  145. 'igrlex': igrlex,
  146. 'igrevlex': igrevlex
  147. }
  148. def monomial_key(order=None, gens=None):
  149. """
  150. Return a function defining admissible order on monomials.
  151. The result of a call to :func:`monomial_key` is a function which should
  152. be used as a key to :func:`sorted` built-in function, to provide order
  153. in a set of monomials of the same length.
  154. Currently supported monomial orderings are:
  155. 1. lex - lexicographic order (default)
  156. 2. grlex - graded lexicographic order
  157. 3. grevlex - reversed graded lexicographic order
  158. 4. ilex, igrlex, igrevlex - the corresponding inverse orders
  159. If the ``order`` input argument is not a string but has ``__call__``
  160. attribute, then it will pass through with an assumption that the
  161. callable object defines an admissible order on monomials.
  162. If the ``gens`` input argument contains a list of generators, the
  163. resulting key function can be used to sort SymPy ``Expr`` objects.
  164. """
  165. if order is None:
  166. order = lex
  167. if isinstance(order, Symbol):
  168. order = str(order)
  169. if isinstance(order, str):
  170. try:
  171. order = _monomial_key[order]
  172. except KeyError:
  173. raise ValueError("supported monomial orderings are 'lex', 'grlex' and 'grevlex', got %r" % order)
  174. if hasattr(order, '__call__'):
  175. if gens is not None:
  176. def _order(expr):
  177. return order(expr.as_poly(*gens).degree_list())
  178. return _order
  179. return order
  180. else:
  181. raise ValueError("monomial ordering specification must be a string or a callable, got %s" % order)
  182. class _ItemGetter:
  183. """Helper class to return a subsequence of values."""
  184. def __init__(self, seq):
  185. self.seq = tuple(seq)
  186. def __call__(self, m):
  187. return tuple(m[idx] for idx in self.seq)
  188. def __eq__(self, other):
  189. if not isinstance(other, _ItemGetter):
  190. return False
  191. return self.seq == other.seq
  192. def build_product_order(arg, gens):
  193. """
  194. Build a monomial order on ``gens``.
  195. ``arg`` should be a tuple of iterables. The first element of each iterable
  196. should be a string or monomial order (will be passed to monomial_key),
  197. the others should be subsets of the generators. This function will build
  198. the corresponding product order.
  199. For example, build a product of two grlex orders:
  200. >>> from sympy.polys.orderings import build_product_order
  201. >>> from sympy.abc import x, y, z, t
  202. >>> O = build_product_order((("grlex", x, y), ("grlex", z, t)), [x, y, z, t])
  203. >>> O((1, 2, 3, 4))
  204. ((3, (1, 2)), (7, (3, 4)))
  205. """
  206. gens2idx = {}
  207. for i, g in enumerate(gens):
  208. gens2idx[g] = i
  209. order = []
  210. for expr in arg:
  211. name = expr[0]
  212. var = expr[1:]
  213. def makelambda(var):
  214. return _ItemGetter(gens2idx[g] for g in var)
  215. order.append((monomial_key(name), makelambda(var)))
  216. return ProductOrder(*order)