|
|
"""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]
|