123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- """Tools for constructing domains for expressions. """
- from sympy.core import sympify
- from sympy.core.evalf import pure_complex
- from sympy.core.sorting import ordered
- from sympy.polys.domains import ZZ, QQ, ZZ_I, QQ_I, EX
- from sympy.polys.domains.complexfield import ComplexField
- from sympy.polys.domains.realfield import RealField
- from sympy.polys.polyoptions import build_options
- from sympy.polys.polyutils import parallel_dict_from_basic
- from sympy.utilities import public
- def _construct_simple(coeffs, opt):
- """Handle simple domains, e.g.: ZZ, QQ, RR and algebraic domains. """
- rationals = floats = complexes = algebraics = False
- float_numbers = []
- if opt.extension is True:
- is_algebraic = lambda coeff: coeff.is_number and coeff.is_algebraic
- else:
- is_algebraic = lambda coeff: False
- for coeff in coeffs:
- if coeff.is_Rational:
- if not coeff.is_Integer:
- rationals = True
- elif coeff.is_Float:
- if algebraics:
- # there are both reals and algebraics -> EX
- return False
- else:
- floats = True
- float_numbers.append(coeff)
- else:
- is_complex = pure_complex(coeff)
- if is_complex:
- complexes = True
- x, y = is_complex
- if x.is_Rational and y.is_Rational:
- if not (x.is_Integer and y.is_Integer):
- rationals = True
- continue
- else:
- floats = True
- if x.is_Float:
- float_numbers.append(x)
- if y.is_Float:
- float_numbers.append(y)
- elif is_algebraic(coeff):
- if floats:
- # there are both algebraics and reals -> EX
- return False
- algebraics = True
- else:
- # this is a composite domain, e.g. ZZ[X], EX
- return None
- # Use the maximum precision of all coefficients for the RR or CC
- # precision
- max_prec = max(c._prec for c in float_numbers) if float_numbers else 53
- if algebraics:
- domain, result = _construct_algebraic(coeffs, opt)
- else:
- if floats and complexes:
- domain = ComplexField(prec=max_prec)
- elif floats:
- domain = RealField(prec=max_prec)
- elif rationals or opt.field:
- domain = QQ_I if complexes else QQ
- else:
- domain = ZZ_I if complexes else ZZ
- result = [domain.from_sympy(coeff) for coeff in coeffs]
- return domain, result
- def _construct_algebraic(coeffs, opt):
- """We know that coefficients are algebraic so construct the extension. """
- from sympy.polys.numberfields import primitive_element
- exts = set()
- def build_trees(args):
- trees = []
- for a in args:
- if a.is_Rational:
- tree = ('Q', QQ.from_sympy(a))
- elif a.is_Add:
- tree = ('+', build_trees(a.args))
- elif a.is_Mul:
- tree = ('*', build_trees(a.args))
- else:
- tree = ('e', a)
- exts.add(a)
- trees.append(tree)
- return trees
- trees = build_trees(coeffs)
- exts = list(ordered(exts))
- g, span, H = primitive_element(exts, ex=True, polys=True)
- root = sum([ s*ext for s, ext in zip(span, exts) ])
- domain, g = QQ.algebraic_field((g, root)), g.rep.rep
- exts_dom = [domain.dtype.from_list(h, g, QQ) for h in H]
- exts_map = dict(zip(exts, exts_dom))
- def convert_tree(tree):
- op, args = tree
- if op == 'Q':
- return domain.dtype.from_list([args], g, QQ)
- elif op == '+':
- return sum((convert_tree(a) for a in args), domain.zero)
- elif op == '*':
- # return prod(convert(a) for a in args)
- t = convert_tree(args[0])
- for a in args[1:]:
- t *= convert_tree(a)
- return t
- elif op == 'e':
- return exts_map[args]
- else:
- raise RuntimeError
- result = [convert_tree(tree) for tree in trees]
- return domain, result
- def _construct_composite(coeffs, opt):
- """Handle composite domains, e.g.: ZZ[X], QQ[X], ZZ(X), QQ(X). """
- numers, denoms = [], []
- for coeff in coeffs:
- numer, denom = coeff.as_numer_denom()
- numers.append(numer)
- denoms.append(denom)
- polys, gens = parallel_dict_from_basic(numers + denoms) # XXX: sorting
- if not gens:
- return None
- if opt.composite is None:
- if any(gen.is_number and gen.is_algebraic for gen in gens):
- return None # generators are number-like so lets better use EX
- all_symbols = set()
- for gen in gens:
- symbols = gen.free_symbols
- if all_symbols & symbols:
- return None # there could be algebraic relations between generators
- else:
- all_symbols |= symbols
- n = len(gens)
- k = len(polys)//2
- numers = polys[:k]
- denoms = polys[k:]
- if opt.field:
- fractions = True
- else:
- fractions, zeros = False, (0,)*n
- for denom in denoms:
- if len(denom) > 1 or zeros not in denom:
- fractions = True
- break
- coeffs = set()
- if not fractions:
- for numer, denom in zip(numers, denoms):
- denom = denom[zeros]
- for monom, coeff in numer.items():
- coeff /= denom
- coeffs.add(coeff)
- numer[monom] = coeff
- else:
- for numer, denom in zip(numers, denoms):
- coeffs.update(list(numer.values()))
- coeffs.update(list(denom.values()))
- rationals = floats = complexes = False
- float_numbers = []
- for coeff in coeffs:
- if coeff.is_Rational:
- if not coeff.is_Integer:
- rationals = True
- elif coeff.is_Float:
- floats = True
- float_numbers.append(coeff)
- else:
- is_complex = pure_complex(coeff)
- if is_complex is not None:
- complexes = True
- x, y = is_complex
- if x.is_Rational and y.is_Rational:
- if not (x.is_Integer and y.is_Integer):
- rationals = True
- else:
- floats = True
- if x.is_Float:
- float_numbers.append(x)
- if y.is_Float:
- float_numbers.append(y)
- max_prec = max(c._prec for c in float_numbers) if float_numbers else 53
- if floats and complexes:
- ground = ComplexField(prec=max_prec)
- elif floats:
- ground = RealField(prec=max_prec)
- elif complexes:
- if rationals:
- ground = QQ_I
- else:
- ground = ZZ_I
- elif rationals:
- ground = QQ
- else:
- ground = ZZ
- result = []
- if not fractions:
- domain = ground.poly_ring(*gens)
- for numer in numers:
- for monom, coeff in numer.items():
- numer[monom] = ground.from_sympy(coeff)
- result.append(domain(numer))
- else:
- domain = ground.frac_field(*gens)
- for numer, denom in zip(numers, denoms):
- for monom, coeff in numer.items():
- numer[monom] = ground.from_sympy(coeff)
- for monom, coeff in denom.items():
- denom[monom] = ground.from_sympy(coeff)
- result.append(domain((numer, denom)))
- return domain, result
- def _construct_expression(coeffs, opt):
- """The last resort case, i.e. use the expression domain. """
- domain, result = EX, []
- for coeff in coeffs:
- result.append(domain.from_sympy(coeff))
- return domain, result
- @public
- def construct_domain(obj, **args):
- """Construct a minimal domain for a list of expressions.
- Explanation
- ===========
- Given a list of normal SymPy expressions (of type :py:class:`~.Expr`)
- ``construct_domain`` will find a minimal :py:class:`~.Domain` that can
- represent those expressions. The expressions will be converted to elements
- of the domain and both the domain and the domain elements are returned.
- Parameters
- ==========
- obj: list or dict
- The expressions to build a domain for.
- **args: keyword arguments
- Options that affect the choice of domain.
- Returns
- =======
- (K, elements): Domain and list of domain elements
- The domain K that can represent the expressions and the list or dict
- of domain elements representing the same expressions as elements of K.
- Examples
- ========
- Given a list of :py:class:`~.Integer` ``construct_domain`` will return the
- domain :ref:`ZZ` and a list of integers as elements of :ref:`ZZ`.
- >>> from sympy import construct_domain, S
- >>> expressions = [S(2), S(3), S(4)]
- >>> K, elements = construct_domain(expressions)
- >>> K
- ZZ
- >>> elements
- [2, 3, 4]
- >>> type(elements[0]) # doctest: +SKIP
- <class 'int'>
- >>> type(expressions[0])
- <class 'sympy.core.numbers.Integer'>
- If there are any :py:class:`~.Rational` then :ref:`QQ` is returned
- instead.
- >>> construct_domain([S(1)/2, S(3)/4])
- (QQ, [1/2, 3/4])
- If there are symbols then a polynomial ring :ref:`K[x]` is returned.
- >>> from sympy import symbols
- >>> x, y = symbols('x, y')
- >>> construct_domain([2*x + 1, S(3)/4])
- (QQ[x], [2*x + 1, 3/4])
- >>> construct_domain([2*x + 1, y])
- (ZZ[x,y], [2*x + 1, y])
- If any symbols appear with negative powers then a rational function field
- :ref:`K(x)` will be returned.
- >>> construct_domain([y/x, x/(1 - y)])
- (ZZ(x,y), [y/x, -x/(y - 1)])
- Irrational algebraic numbers will result in the :ref:`EX` domain by
- default. The keyword argument ``extension=True`` leads to the construction
- of an algebraic number field :ref:`QQ(a)`.
- >>> from sympy import sqrt
- >>> construct_domain([sqrt(2)])
- (EX, [EX(sqrt(2))])
- >>> construct_domain([sqrt(2)], extension=True) # doctest: +SKIP
- (QQ<sqrt(2)>, [ANP([1, 0], [1, 0, -2], QQ)])
- See also
- ========
- Domain
- Expr
- """
- opt = build_options(args)
- if hasattr(obj, '__iter__'):
- if isinstance(obj, dict):
- if not obj:
- monoms, coeffs = [], []
- else:
- monoms, coeffs = list(zip(*list(obj.items())))
- else:
- coeffs = obj
- else:
- coeffs = [obj]
- coeffs = list(map(sympify, coeffs))
- result = _construct_simple(coeffs, opt)
- if result is not None:
- if result is not False:
- domain, coeffs = result
- else:
- domain, coeffs = _construct_expression(coeffs, opt)
- else:
- if opt.composite is False:
- result = None
- else:
- result = _construct_composite(coeffs, opt)
- if result is not None:
- domain, coeffs = result
- else:
- domain, coeffs = _construct_expression(coeffs, opt)
- if hasattr(obj, '__iter__'):
- if isinstance(obj, dict):
- return domain, dict(list(zip(monoms, coeffs)))
- else:
- return domain, coeffs
- else:
- return domain, coeffs[0]
|