You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
11 KiB

"""Tools for constructing domains for expressions. """
from math import prod
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_tree(a) for a in args)
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]