constructor.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. """Tools for constructing domains for expressions. """
  2. from math import prod
  3. from sympy.core import sympify
  4. from sympy.core.evalf import pure_complex
  5. from sympy.core.sorting import ordered
  6. from sympy.polys.domains import ZZ, QQ, ZZ_I, QQ_I, EX
  7. from sympy.polys.domains.complexfield import ComplexField
  8. from sympy.polys.domains.realfield import RealField
  9. from sympy.polys.polyoptions import build_options
  10. from sympy.polys.polyutils import parallel_dict_from_basic
  11. from sympy.utilities import public
  12. def _construct_simple(coeffs, opt):
  13. """Handle simple domains, e.g.: ZZ, QQ, RR and algebraic domains. """
  14. rationals = floats = complexes = algebraics = False
  15. float_numbers = []
  16. if opt.extension is True:
  17. is_algebraic = lambda coeff: coeff.is_number and coeff.is_algebraic
  18. else:
  19. is_algebraic = lambda coeff: False
  20. for coeff in coeffs:
  21. if coeff.is_Rational:
  22. if not coeff.is_Integer:
  23. rationals = True
  24. elif coeff.is_Float:
  25. if algebraics:
  26. # there are both reals and algebraics -> EX
  27. return False
  28. else:
  29. floats = True
  30. float_numbers.append(coeff)
  31. else:
  32. is_complex = pure_complex(coeff)
  33. if is_complex:
  34. complexes = True
  35. x, y = is_complex
  36. if x.is_Rational and y.is_Rational:
  37. if not (x.is_Integer and y.is_Integer):
  38. rationals = True
  39. continue
  40. else:
  41. floats = True
  42. if x.is_Float:
  43. float_numbers.append(x)
  44. if y.is_Float:
  45. float_numbers.append(y)
  46. elif is_algebraic(coeff):
  47. if floats:
  48. # there are both algebraics and reals -> EX
  49. return False
  50. algebraics = True
  51. else:
  52. # this is a composite domain, e.g. ZZ[X], EX
  53. return None
  54. # Use the maximum precision of all coefficients for the RR or CC
  55. # precision
  56. max_prec = max(c._prec for c in float_numbers) if float_numbers else 53
  57. if algebraics:
  58. domain, result = _construct_algebraic(coeffs, opt)
  59. else:
  60. if floats and complexes:
  61. domain = ComplexField(prec=max_prec)
  62. elif floats:
  63. domain = RealField(prec=max_prec)
  64. elif rationals or opt.field:
  65. domain = QQ_I if complexes else QQ
  66. else:
  67. domain = ZZ_I if complexes else ZZ
  68. result = [domain.from_sympy(coeff) for coeff in coeffs]
  69. return domain, result
  70. def _construct_algebraic(coeffs, opt):
  71. """We know that coefficients are algebraic so construct the extension. """
  72. from sympy.polys.numberfields import primitive_element
  73. exts = set()
  74. def build_trees(args):
  75. trees = []
  76. for a in args:
  77. if a.is_Rational:
  78. tree = ('Q', QQ.from_sympy(a))
  79. elif a.is_Add:
  80. tree = ('+', build_trees(a.args))
  81. elif a.is_Mul:
  82. tree = ('*', build_trees(a.args))
  83. else:
  84. tree = ('e', a)
  85. exts.add(a)
  86. trees.append(tree)
  87. return trees
  88. trees = build_trees(coeffs)
  89. exts = list(ordered(exts))
  90. g, span, H = primitive_element(exts, ex=True, polys=True)
  91. root = sum([ s*ext for s, ext in zip(span, exts) ])
  92. domain, g = QQ.algebraic_field((g, root)), g.rep.rep
  93. exts_dom = [domain.dtype.from_list(h, g, QQ) for h in H]
  94. exts_map = dict(zip(exts, exts_dom))
  95. def convert_tree(tree):
  96. op, args = tree
  97. if op == 'Q':
  98. return domain.dtype.from_list([args], g, QQ)
  99. elif op == '+':
  100. return sum((convert_tree(a) for a in args), domain.zero)
  101. elif op == '*':
  102. return prod(convert_tree(a) for a in args)
  103. elif op == 'e':
  104. return exts_map[args]
  105. else:
  106. raise RuntimeError
  107. result = [convert_tree(tree) for tree in trees]
  108. return domain, result
  109. def _construct_composite(coeffs, opt):
  110. """Handle composite domains, e.g.: ZZ[X], QQ[X], ZZ(X), QQ(X). """
  111. numers, denoms = [], []
  112. for coeff in coeffs:
  113. numer, denom = coeff.as_numer_denom()
  114. numers.append(numer)
  115. denoms.append(denom)
  116. polys, gens = parallel_dict_from_basic(numers + denoms) # XXX: sorting
  117. if not gens:
  118. return None
  119. if opt.composite is None:
  120. if any(gen.is_number and gen.is_algebraic for gen in gens):
  121. return None # generators are number-like so lets better use EX
  122. all_symbols = set()
  123. for gen in gens:
  124. symbols = gen.free_symbols
  125. if all_symbols & symbols:
  126. return None # there could be algebraic relations between generators
  127. else:
  128. all_symbols |= symbols
  129. n = len(gens)
  130. k = len(polys)//2
  131. numers = polys[:k]
  132. denoms = polys[k:]
  133. if opt.field:
  134. fractions = True
  135. else:
  136. fractions, zeros = False, (0,)*n
  137. for denom in denoms:
  138. if len(denom) > 1 or zeros not in denom:
  139. fractions = True
  140. break
  141. coeffs = set()
  142. if not fractions:
  143. for numer, denom in zip(numers, denoms):
  144. denom = denom[zeros]
  145. for monom, coeff in numer.items():
  146. coeff /= denom
  147. coeffs.add(coeff)
  148. numer[monom] = coeff
  149. else:
  150. for numer, denom in zip(numers, denoms):
  151. coeffs.update(list(numer.values()))
  152. coeffs.update(list(denom.values()))
  153. rationals = floats = complexes = False
  154. float_numbers = []
  155. for coeff in coeffs:
  156. if coeff.is_Rational:
  157. if not coeff.is_Integer:
  158. rationals = True
  159. elif coeff.is_Float:
  160. floats = True
  161. float_numbers.append(coeff)
  162. else:
  163. is_complex = pure_complex(coeff)
  164. if is_complex is not None:
  165. complexes = True
  166. x, y = is_complex
  167. if x.is_Rational and y.is_Rational:
  168. if not (x.is_Integer and y.is_Integer):
  169. rationals = True
  170. else:
  171. floats = True
  172. if x.is_Float:
  173. float_numbers.append(x)
  174. if y.is_Float:
  175. float_numbers.append(y)
  176. max_prec = max(c._prec for c in float_numbers) if float_numbers else 53
  177. if floats and complexes:
  178. ground = ComplexField(prec=max_prec)
  179. elif floats:
  180. ground = RealField(prec=max_prec)
  181. elif complexes:
  182. if rationals:
  183. ground = QQ_I
  184. else:
  185. ground = ZZ_I
  186. elif rationals:
  187. ground = QQ
  188. else:
  189. ground = ZZ
  190. result = []
  191. if not fractions:
  192. domain = ground.poly_ring(*gens)
  193. for numer in numers:
  194. for monom, coeff in numer.items():
  195. numer[monom] = ground.from_sympy(coeff)
  196. result.append(domain(numer))
  197. else:
  198. domain = ground.frac_field(*gens)
  199. for numer, denom in zip(numers, denoms):
  200. for monom, coeff in numer.items():
  201. numer[monom] = ground.from_sympy(coeff)
  202. for monom, coeff in denom.items():
  203. denom[monom] = ground.from_sympy(coeff)
  204. result.append(domain((numer, denom)))
  205. return domain, result
  206. def _construct_expression(coeffs, opt):
  207. """The last resort case, i.e. use the expression domain. """
  208. domain, result = EX, []
  209. for coeff in coeffs:
  210. result.append(domain.from_sympy(coeff))
  211. return domain, result
  212. @public
  213. def construct_domain(obj, **args):
  214. """Construct a minimal domain for a list of expressions.
  215. Explanation
  216. ===========
  217. Given a list of normal SymPy expressions (of type :py:class:`~.Expr`)
  218. ``construct_domain`` will find a minimal :py:class:`~.Domain` that can
  219. represent those expressions. The expressions will be converted to elements
  220. of the domain and both the domain and the domain elements are returned.
  221. Parameters
  222. ==========
  223. obj: list or dict
  224. The expressions to build a domain for.
  225. **args: keyword arguments
  226. Options that affect the choice of domain.
  227. Returns
  228. =======
  229. (K, elements): Domain and list of domain elements
  230. The domain K that can represent the expressions and the list or dict
  231. of domain elements representing the same expressions as elements of K.
  232. Examples
  233. ========
  234. Given a list of :py:class:`~.Integer` ``construct_domain`` will return the
  235. domain :ref:`ZZ` and a list of integers as elements of :ref:`ZZ`.
  236. >>> from sympy import construct_domain, S
  237. >>> expressions = [S(2), S(3), S(4)]
  238. >>> K, elements = construct_domain(expressions)
  239. >>> K
  240. ZZ
  241. >>> elements
  242. [2, 3, 4]
  243. >>> type(elements[0]) # doctest: +SKIP
  244. <class 'int'>
  245. >>> type(expressions[0])
  246. <class 'sympy.core.numbers.Integer'>
  247. If there are any :py:class:`~.Rational` then :ref:`QQ` is returned
  248. instead.
  249. >>> construct_domain([S(1)/2, S(3)/4])
  250. (QQ, [1/2, 3/4])
  251. If there are symbols then a polynomial ring :ref:`K[x]` is returned.
  252. >>> from sympy import symbols
  253. >>> x, y = symbols('x, y')
  254. >>> construct_domain([2*x + 1, S(3)/4])
  255. (QQ[x], [2*x + 1, 3/4])
  256. >>> construct_domain([2*x + 1, y])
  257. (ZZ[x,y], [2*x + 1, y])
  258. If any symbols appear with negative powers then a rational function field
  259. :ref:`K(x)` will be returned.
  260. >>> construct_domain([y/x, x/(1 - y)])
  261. (ZZ(x,y), [y/x, -x/(y - 1)])
  262. Irrational algebraic numbers will result in the :ref:`EX` domain by
  263. default. The keyword argument ``extension=True`` leads to the construction
  264. of an algebraic number field :ref:`QQ(a)`.
  265. >>> from sympy import sqrt
  266. >>> construct_domain([sqrt(2)])
  267. (EX, [EX(sqrt(2))])
  268. >>> construct_domain([sqrt(2)], extension=True) # doctest: +SKIP
  269. (QQ<sqrt(2)>, [ANP([1, 0], [1, 0, -2], QQ)])
  270. See also
  271. ========
  272. Domain
  273. Expr
  274. """
  275. opt = build_options(args)
  276. if hasattr(obj, '__iter__'):
  277. if isinstance(obj, dict):
  278. if not obj:
  279. monoms, coeffs = [], []
  280. else:
  281. monoms, coeffs = list(zip(*list(obj.items())))
  282. else:
  283. coeffs = obj
  284. else:
  285. coeffs = [obj]
  286. coeffs = list(map(sympify, coeffs))
  287. result = _construct_simple(coeffs, opt)
  288. if result is not None:
  289. if result is not False:
  290. domain, coeffs = result
  291. else:
  292. domain, coeffs = _construct_expression(coeffs, opt)
  293. else:
  294. if opt.composite is False:
  295. result = None
  296. else:
  297. result = _construct_composite(coeffs, opt)
  298. if result is not None:
  299. domain, coeffs = result
  300. else:
  301. domain, coeffs = _construct_expression(coeffs, opt)
  302. if hasattr(obj, '__iter__'):
  303. if isinstance(obj, dict):
  304. return domain, dict(list(zip(monoms, coeffs)))
  305. else:
  306. return domain, coeffs
  307. else:
  308. return domain, coeffs[0]