"""Tools for constructing domains for expressions. """
from __future__ import print_function, division
from sympy.core import sympify
from sympy.polys.domains import ZZ, QQ, EX
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. """
result, rationals, reals, algebraics = {}, False, False, False
if opt.extension is True:
is_algebraic = lambda coeff: coeff.is_number and coeff.is_algebraic
else:
is_algebraic = lambda coeff: False
# XXX: add support for a + b*I coefficients
for coeff in coeffs:
if coeff.is_Rational:
if not coeff.is_Integer:
rationals = True
elif coeff.is_Float:
if not algebraics:
reals = True
else:
# there are both reals and algebraics -> EX
return False
elif is_algebraic(coeff):
if not reals:
algebraics = True
else:
# there are both algebraics and reals -> EX
return False
else:
# this is a composite domain, e.g. ZZ[X], EX
return None
if algebraics:
domain, result = _construct_algebraic(coeffs, opt)
else:
if reals:
# Use the maximum precision of all coefficients for the RR's
# precision
max_prec = max([c._prec for c in coeffs])
domain = RealField(prec=max_prec)
else:
if opt.field or rationals:
domain = QQ
else:
domain = ZZ
result = []
for coeff in coeffs:
result.append(domain.from_sympy(coeff))
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
result, exts = [], set([])
for coeff in coeffs:
if coeff.is_Rational:
coeff = (None, 0, QQ.from_sympy(coeff))
else:
a = coeff.as_coeff_add()[0]
coeff -= a
b = coeff.as_coeff_mul()[0]
coeff /= b
exts.add(coeff)
a = QQ.from_sympy(a)
b = QQ.from_sympy(b)
coeff = (coeff, b, a)
result.append(coeff)
exts = list(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
for i, (coeff, a, b) in enumerate(result):
if coeff is not None:
coeff = a*domain.dtype.from_list(H[exts.index(coeff)], g, QQ) + b
else:
coeff = domain.dtype.from_list([b], g, QQ)
result[i] = coeff
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, reals = False, False
for coeff in coeffs:
if coeff.is_Rational:
if not coeff.is_Integer:
rationals = True
elif coeff.is_Float:
reals = True
break
if reals:
max_prec = max([c._prec for c in coeffs])
ground = RealField(prec=max_prec)
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
[docs]@public
def construct_domain(obj, **args):
"""Construct a minimal domain for the list of coefficients. """
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]