polyutils.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. """Useful utilities for higher level polynomial classes. """
  2. from sympy.core import (S, Add, Mul, Pow, Eq, Expr,
  3. expand_mul, expand_multinomial)
  4. from sympy.core.exprtools import decompose_power, decompose_power_rat
  5. from sympy.polys.polyerrors import PolynomialError, GeneratorsError
  6. from sympy.polys.polyoptions import build_options
  7. import re
  8. _gens_order = {
  9. 'a': 301, 'b': 302, 'c': 303, 'd': 304,
  10. 'e': 305, 'f': 306, 'g': 307, 'h': 308,
  11. 'i': 309, 'j': 310, 'k': 311, 'l': 312,
  12. 'm': 313, 'n': 314, 'o': 315, 'p': 216,
  13. 'q': 217, 'r': 218, 's': 219, 't': 220,
  14. 'u': 221, 'v': 222, 'w': 223, 'x': 124,
  15. 'y': 125, 'z': 126,
  16. }
  17. _max_order = 1000
  18. _re_gen = re.compile(r"^(.*?)(\d*)$", re.MULTILINE)
  19. def _nsort(roots, separated=False):
  20. """Sort the numerical roots putting the real roots first, then sorting
  21. according to real and imaginary parts. If ``separated`` is True, then
  22. the real and imaginary roots will be returned in two lists, respectively.
  23. This routine tries to avoid issue 6137 by separating the roots into real
  24. and imaginary parts before evaluation. In addition, the sorting will raise
  25. an error if any computation cannot be done with precision.
  26. """
  27. if not all(r.is_number for r in roots):
  28. raise NotImplementedError
  29. # see issue 6137:
  30. # get the real part of the evaluated real and imaginary parts of each root
  31. key = [[i.n(2).as_real_imag()[0] for i in r.as_real_imag()] for r in roots]
  32. # make sure the parts were computed with precision
  33. if len(roots) > 1 and any(i._prec == 1 for k in key for i in k):
  34. raise NotImplementedError("could not compute root with precision")
  35. # insert a key to indicate if the root has an imaginary part
  36. key = [(1 if i else 0, r, i) for r, i in key]
  37. key = sorted(zip(key, roots))
  38. # return the real and imaginary roots separately if desired
  39. if separated:
  40. r = []
  41. i = []
  42. for (im, _, _), v in key:
  43. if im:
  44. i.append(v)
  45. else:
  46. r.append(v)
  47. return r, i
  48. _, roots = zip(*key)
  49. return list(roots)
  50. def _sort_gens(gens, **args):
  51. """Sort generators in a reasonably intelligent way. """
  52. opt = build_options(args)
  53. gens_order, wrt = {}, None
  54. if opt is not None:
  55. gens_order, wrt = {}, opt.wrt
  56. for i, gen in enumerate(opt.sort):
  57. gens_order[gen] = i + 1
  58. def order_key(gen):
  59. gen = str(gen)
  60. if wrt is not None:
  61. try:
  62. return (-len(wrt) + wrt.index(gen), gen, 0)
  63. except ValueError:
  64. pass
  65. name, index = _re_gen.match(gen).groups()
  66. if index:
  67. index = int(index)
  68. else:
  69. index = 0
  70. try:
  71. return ( gens_order[name], name, index)
  72. except KeyError:
  73. pass
  74. try:
  75. return (_gens_order[name], name, index)
  76. except KeyError:
  77. pass
  78. return (_max_order, name, index)
  79. try:
  80. gens = sorted(gens, key=order_key)
  81. except TypeError: # pragma: no cover
  82. pass
  83. return tuple(gens)
  84. def _unify_gens(f_gens, g_gens):
  85. """Unify generators in a reasonably intelligent way. """
  86. f_gens = list(f_gens)
  87. g_gens = list(g_gens)
  88. if f_gens == g_gens:
  89. return tuple(f_gens)
  90. gens, common, k = [], [], 0
  91. for gen in f_gens:
  92. if gen in g_gens:
  93. common.append(gen)
  94. for i, gen in enumerate(g_gens):
  95. if gen in common:
  96. g_gens[i], k = common[k], k + 1
  97. for gen in common:
  98. i = f_gens.index(gen)
  99. gens.extend(f_gens[:i])
  100. f_gens = f_gens[i + 1:]
  101. i = g_gens.index(gen)
  102. gens.extend(g_gens[:i])
  103. g_gens = g_gens[i + 1:]
  104. gens.append(gen)
  105. gens.extend(f_gens)
  106. gens.extend(g_gens)
  107. return tuple(gens)
  108. def _analyze_gens(gens):
  109. """Support for passing generators as `*gens` and `[gens]`. """
  110. if len(gens) == 1 and hasattr(gens[0], '__iter__'):
  111. return tuple(gens[0])
  112. else:
  113. return tuple(gens)
  114. def _sort_factors(factors, **args):
  115. """Sort low-level factors in increasing 'complexity' order. """
  116. def order_if_multiple_key(factor):
  117. (f, n) = factor
  118. return (len(f), n, f)
  119. def order_no_multiple_key(f):
  120. return (len(f), f)
  121. if args.get('multiple', True):
  122. return sorted(factors, key=order_if_multiple_key)
  123. else:
  124. return sorted(factors, key=order_no_multiple_key)
  125. illegal = [S.NaN, S.Infinity, S.NegativeInfinity, S.ComplexInfinity]
  126. illegal_types = [type(obj) for obj in illegal]
  127. finf = [float(i) for i in illegal[1:3]]
  128. def _not_a_coeff(expr):
  129. """Do not treat NaN and infinities as valid polynomial coefficients. """
  130. if type(expr) in illegal_types or expr in finf:
  131. return True
  132. if isinstance(expr, float) and float(expr) != expr:
  133. return True # nan
  134. return # could be
  135. def _parallel_dict_from_expr_if_gens(exprs, opt):
  136. """Transform expressions into a multinomial form given generators. """
  137. k, indices = len(opt.gens), {}
  138. for i, g in enumerate(opt.gens):
  139. indices[g] = i
  140. polys = []
  141. for expr in exprs:
  142. poly = {}
  143. if expr.is_Equality:
  144. expr = expr.lhs - expr.rhs
  145. for term in Add.make_args(expr):
  146. coeff, monom = [], [0]*k
  147. for factor in Mul.make_args(term):
  148. if not _not_a_coeff(factor) and factor.is_Number:
  149. coeff.append(factor)
  150. else:
  151. try:
  152. if opt.series is False:
  153. base, exp = decompose_power(factor)
  154. if exp < 0:
  155. exp, base = -exp, Pow(base, -S.One)
  156. else:
  157. base, exp = decompose_power_rat(factor)
  158. monom[indices[base]] = exp
  159. except KeyError:
  160. if not factor.has_free(*opt.gens):
  161. coeff.append(factor)
  162. else:
  163. raise PolynomialError("%s contains an element of "
  164. "the set of generators." % factor)
  165. monom = tuple(monom)
  166. if monom in poly:
  167. poly[monom] += Mul(*coeff)
  168. else:
  169. poly[monom] = Mul(*coeff)
  170. polys.append(poly)
  171. return polys, opt.gens
  172. def _parallel_dict_from_expr_no_gens(exprs, opt):
  173. """Transform expressions into a multinomial form and figure out generators. """
  174. if opt.domain is not None:
  175. def _is_coeff(factor):
  176. return factor in opt.domain
  177. elif opt.extension is True:
  178. def _is_coeff(factor):
  179. return factor.is_algebraic
  180. elif opt.greedy is not False:
  181. def _is_coeff(factor):
  182. return factor is S.ImaginaryUnit
  183. else:
  184. def _is_coeff(factor):
  185. return factor.is_number
  186. gens, reprs = set(), []
  187. for expr in exprs:
  188. terms = []
  189. if expr.is_Equality:
  190. expr = expr.lhs - expr.rhs
  191. for term in Add.make_args(expr):
  192. coeff, elements = [], {}
  193. for factor in Mul.make_args(term):
  194. if not _not_a_coeff(factor) and (factor.is_Number or _is_coeff(factor)):
  195. coeff.append(factor)
  196. else:
  197. if opt.series is False:
  198. base, exp = decompose_power(factor)
  199. if exp < 0:
  200. exp, base = -exp, Pow(base, -S.One)
  201. else:
  202. base, exp = decompose_power_rat(factor)
  203. elements[base] = elements.setdefault(base, 0) + exp
  204. gens.add(base)
  205. terms.append((coeff, elements))
  206. reprs.append(terms)
  207. gens = _sort_gens(gens, opt=opt)
  208. k, indices = len(gens), {}
  209. for i, g in enumerate(gens):
  210. indices[g] = i
  211. polys = []
  212. for terms in reprs:
  213. poly = {}
  214. for coeff, term in terms:
  215. monom = [0]*k
  216. for base, exp in term.items():
  217. monom[indices[base]] = exp
  218. monom = tuple(monom)
  219. if monom in poly:
  220. poly[monom] += Mul(*coeff)
  221. else:
  222. poly[monom] = Mul(*coeff)
  223. polys.append(poly)
  224. return polys, tuple(gens)
  225. def _dict_from_expr_if_gens(expr, opt):
  226. """Transform an expression into a multinomial form given generators. """
  227. (poly,), gens = _parallel_dict_from_expr_if_gens((expr,), opt)
  228. return poly, gens
  229. def _dict_from_expr_no_gens(expr, opt):
  230. """Transform an expression into a multinomial form and figure out generators. """
  231. (poly,), gens = _parallel_dict_from_expr_no_gens((expr,), opt)
  232. return poly, gens
  233. def parallel_dict_from_expr(exprs, **args):
  234. """Transform expressions into a multinomial form. """
  235. reps, opt = _parallel_dict_from_expr(exprs, build_options(args))
  236. return reps, opt.gens
  237. def _parallel_dict_from_expr(exprs, opt):
  238. """Transform expressions into a multinomial form. """
  239. if opt.expand is not False:
  240. exprs = [ expr.expand() for expr in exprs ]
  241. if any(expr.is_commutative is False for expr in exprs):
  242. raise PolynomialError('non-commutative expressions are not supported')
  243. if opt.gens:
  244. reps, gens = _parallel_dict_from_expr_if_gens(exprs, opt)
  245. else:
  246. reps, gens = _parallel_dict_from_expr_no_gens(exprs, opt)
  247. return reps, opt.clone({'gens': gens})
  248. def dict_from_expr(expr, **args):
  249. """Transform an expression into a multinomial form. """
  250. rep, opt = _dict_from_expr(expr, build_options(args))
  251. return rep, opt.gens
  252. def _dict_from_expr(expr, opt):
  253. """Transform an expression into a multinomial form. """
  254. if expr.is_commutative is False:
  255. raise PolynomialError('non-commutative expressions are not supported')
  256. def _is_expandable_pow(expr):
  257. return (expr.is_Pow and expr.exp.is_positive and expr.exp.is_Integer
  258. and expr.base.is_Add)
  259. if opt.expand is not False:
  260. if not isinstance(expr, (Expr, Eq)):
  261. raise PolynomialError('expression must be of type Expr')
  262. expr = expr.expand()
  263. # TODO: Integrate this into expand() itself
  264. while any(_is_expandable_pow(i) or i.is_Mul and
  265. any(_is_expandable_pow(j) for j in i.args) for i in
  266. Add.make_args(expr)):
  267. expr = expand_multinomial(expr)
  268. while any(i.is_Mul and any(j.is_Add for j in i.args) for i in Add.make_args(expr)):
  269. expr = expand_mul(expr)
  270. if opt.gens:
  271. rep, gens = _dict_from_expr_if_gens(expr, opt)
  272. else:
  273. rep, gens = _dict_from_expr_no_gens(expr, opt)
  274. return rep, opt.clone({'gens': gens})
  275. def expr_from_dict(rep, *gens):
  276. """Convert a multinomial form into an expression. """
  277. result = []
  278. for monom, coeff in rep.items():
  279. term = [coeff]
  280. for g, m in zip(gens, monom):
  281. if m:
  282. term.append(Pow(g, m))
  283. result.append(Mul(*term))
  284. return Add(*result)
  285. parallel_dict_from_basic = parallel_dict_from_expr
  286. dict_from_basic = dict_from_expr
  287. basic_from_dict = expr_from_dict
  288. def _dict_reorder(rep, gens, new_gens):
  289. """Reorder levels using dict representation. """
  290. gens = list(gens)
  291. monoms = rep.keys()
  292. coeffs = rep.values()
  293. new_monoms = [ [] for _ in range(len(rep)) ]
  294. used_indices = set()
  295. for gen in new_gens:
  296. try:
  297. j = gens.index(gen)
  298. used_indices.add(j)
  299. for M, new_M in zip(monoms, new_monoms):
  300. new_M.append(M[j])
  301. except ValueError:
  302. for new_M in new_monoms:
  303. new_M.append(0)
  304. for i, _ in enumerate(gens):
  305. if i not in used_indices:
  306. for monom in monoms:
  307. if monom[i]:
  308. raise GeneratorsError("unable to drop generators")
  309. return map(tuple, new_monoms), coeffs
  310. class PicklableWithSlots:
  311. """
  312. Mixin class that allows to pickle objects with ``__slots__``.
  313. Examples
  314. ========
  315. First define a class that mixes :class:`PicklableWithSlots` in::
  316. >>> from sympy.polys.polyutils import PicklableWithSlots
  317. >>> class Some(PicklableWithSlots):
  318. ... __slots__ = ('foo', 'bar')
  319. ...
  320. ... def __init__(self, foo, bar):
  321. ... self.foo = foo
  322. ... self.bar = bar
  323. To make :mod:`pickle` happy in doctest we have to use these hacks::
  324. >>> import builtins
  325. >>> builtins.Some = Some
  326. >>> from sympy.polys import polyutils
  327. >>> polyutils.Some = Some
  328. Next lets see if we can create an instance, pickle it and unpickle::
  329. >>> some = Some('abc', 10)
  330. >>> some.foo, some.bar
  331. ('abc', 10)
  332. >>> from pickle import dumps, loads
  333. >>> some2 = loads(dumps(some))
  334. >>> some2.foo, some2.bar
  335. ('abc', 10)
  336. """
  337. __slots__ = ()
  338. def __getstate__(self, cls=None):
  339. if cls is None:
  340. # This is the case for the instance that gets pickled
  341. cls = self.__class__
  342. d = {}
  343. # Get all data that should be stored from super classes
  344. for c in cls.__bases__:
  345. if hasattr(c, "__getstate__"):
  346. d.update(c.__getstate__(self, c))
  347. # Get all information that should be stored from cls and return the dict
  348. for name in cls.__slots__:
  349. if hasattr(self, name):
  350. d[name] = getattr(self, name)
  351. return d
  352. def __setstate__(self, d):
  353. # All values that were pickled are now assigned to a fresh instance
  354. for name, value in d.items():
  355. try:
  356. setattr(self, name, value)
  357. except AttributeError: # This is needed in cases like Rational :> Half
  358. pass
  359. class IntegerPowerable:
  360. r"""
  361. Mixin class for classes that define a `__mul__` method, and want to be
  362. raised to integer powers in the natural way that follows. Implements
  363. powering via binary expansion, for efficiency.
  364. By default, only integer powers $\geq 2$ are supported. To support the
  365. first, zeroth, or negative powers, override the corresponding methods,
  366. `_first_power`, `_zeroth_power`, `_negative_power`, below.
  367. """
  368. def __pow__(self, e, modulo=None):
  369. if e < 2:
  370. try:
  371. if e == 1:
  372. return self._first_power()
  373. elif e == 0:
  374. return self._zeroth_power()
  375. else:
  376. return self._negative_power(e, modulo=modulo)
  377. except NotImplementedError:
  378. return NotImplemented
  379. else:
  380. bits = [int(d) for d in reversed(bin(e)[2:])]
  381. n = len(bits)
  382. p = self
  383. first = True
  384. for i in range(n):
  385. if bits[i]:
  386. if first:
  387. r = p
  388. first = False
  389. else:
  390. r *= p
  391. if modulo is not None:
  392. r %= modulo
  393. if i < n - 1:
  394. p *= p
  395. if modulo is not None:
  396. p %= modulo
  397. return r
  398. def _negative_power(self, e, modulo=None):
  399. """
  400. Compute inverse of self, then raise that to the abs(e) power.
  401. For example, if the class has an `inv()` method,
  402. return self.inv() ** abs(e) % modulo
  403. """
  404. raise NotImplementedError
  405. def _zeroth_power(self):
  406. """Return unity element of algebraic struct to which self belongs."""
  407. raise NotImplementedError
  408. def _first_power(self):
  409. """Return a copy of self."""
  410. raise NotImplementedError