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