Spaces:
Running
Running
File size: 11,373 Bytes
6a86ad5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
"""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.to_list()
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]
|