Coverage for /usr/lib/python3/dist-packages/sympy/polys/fields.py: 21%
429 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Sparse rational function fields. """
3from __future__ import annotations
4from typing import Any
5from functools import reduce
7from operator import add, mul, lt, le, gt, ge
9from sympy.core.expr import Expr
10from sympy.core.mod import Mod
11from sympy.core.numbers import Exp1
12from sympy.core.singleton import S
13from sympy.core.symbol import Symbol
14from sympy.core.sympify import CantSympify, sympify
15from sympy.functions.elementary.exponential import ExpBase
16from sympy.polys.domains.domainelement import DomainElement
17from sympy.polys.domains.fractionfield import FractionField
18from sympy.polys.domains.polynomialring import PolynomialRing
19from sympy.polys.constructor import construct_domain
20from sympy.polys.orderings import lex
21from sympy.polys.polyerrors import CoercionFailed
22from sympy.polys.polyoptions import build_options
23from sympy.polys.polyutils import _parallel_dict_from_expr
24from sympy.polys.rings import PolyElement
25from sympy.printing.defaults import DefaultPrinting
26from sympy.utilities import public
27from sympy.utilities.iterables import is_sequence
28from sympy.utilities.magic import pollute
30@public
31def field(symbols, domain, order=lex):
32 """Construct new rational function field returning (field, x1, ..., xn). """
33 _field = FracField(symbols, domain, order)
34 return (_field,) + _field.gens
36@public
37def xfield(symbols, domain, order=lex):
38 """Construct new rational function field returning (field, (x1, ..., xn)). """
39 _field = FracField(symbols, domain, order)
40 return (_field, _field.gens)
42@public
43def vfield(symbols, domain, order=lex):
44 """Construct new rational function field and inject generators into global namespace. """
45 _field = FracField(symbols, domain, order)
46 pollute([ sym.name for sym in _field.symbols ], _field.gens)
47 return _field
49@public
50def sfield(exprs, *symbols, **options):
51 """Construct a field deriving generators and domain
52 from options and input expressions.
54 Parameters
55 ==========
57 exprs : py:class:`~.Expr` or sequence of :py:class:`~.Expr` (sympifiable)
59 symbols : sequence of :py:class:`~.Symbol`/:py:class:`~.Expr`
61 options : keyword arguments understood by :py:class:`~.Options`
63 Examples
64 ========
66 >>> from sympy import exp, log, symbols, sfield
68 >>> x = symbols("x")
69 >>> K, f = sfield((x*log(x) + 4*x**2)*exp(1/x + log(x)/3)/x**2)
70 >>> K
71 Rational function field in x, exp(1/x), log(x), x**(1/3) over ZZ with lex order
72 >>> f
73 (4*x**2*(exp(1/x)) + x*(exp(1/x))*(log(x)))/((x**(1/3))**5)
74 """
75 single = False
76 if not is_sequence(exprs):
77 exprs, single = [exprs], True
79 exprs = list(map(sympify, exprs))
80 opt = build_options(symbols, options)
81 numdens = []
82 for expr in exprs:
83 numdens.extend(expr.as_numer_denom())
84 reps, opt = _parallel_dict_from_expr(numdens, opt)
86 if opt.domain is None:
87 # NOTE: this is inefficient because construct_domain() automatically
88 # performs conversion to the target domain. It shouldn't do this.
89 coeffs = sum([list(rep.values()) for rep in reps], [])
90 opt.domain, _ = construct_domain(coeffs, opt=opt)
92 _field = FracField(opt.gens, opt.domain, opt.order)
93 fracs = []
94 for i in range(0, len(reps), 2):
95 fracs.append(_field(tuple(reps[i:i+2])))
97 if single:
98 return (_field, fracs[0])
99 else:
100 return (_field, fracs)
102_field_cache: dict[Any, Any] = {}
104class FracField(DefaultPrinting):
105 """Multivariate distributed rational function field. """
107 def __new__(cls, symbols, domain, order=lex):
108 from sympy.polys.rings import PolyRing
109 ring = PolyRing(symbols, domain, order)
110 symbols = ring.symbols
111 ngens = ring.ngens
112 domain = ring.domain
113 order = ring.order
115 _hash_tuple = (cls.__name__, symbols, ngens, domain, order)
116 obj = _field_cache.get(_hash_tuple)
118 if obj is None:
119 obj = object.__new__(cls)
120 obj._hash_tuple = _hash_tuple
121 obj._hash = hash(_hash_tuple)
122 obj.ring = ring
123 obj.dtype = type("FracElement", (FracElement,), {"field": obj})
124 obj.symbols = symbols
125 obj.ngens = ngens
126 obj.domain = domain
127 obj.order = order
129 obj.zero = obj.dtype(ring.zero)
130 obj.one = obj.dtype(ring.one)
132 obj.gens = obj._gens()
134 for symbol, generator in zip(obj.symbols, obj.gens):
135 if isinstance(symbol, Symbol):
136 name = symbol.name
138 if not hasattr(obj, name):
139 setattr(obj, name, generator)
141 _field_cache[_hash_tuple] = obj
143 return obj
145 def _gens(self):
146 """Return a list of polynomial generators. """
147 return tuple([ self.dtype(gen) for gen in self.ring.gens ])
149 def __getnewargs__(self):
150 return (self.symbols, self.domain, self.order)
152 def __hash__(self):
153 return self._hash
155 def index(self, gen):
156 if isinstance(gen, self.dtype):
157 return self.ring.index(gen.to_poly())
158 else:
159 raise ValueError("expected a %s, got %s instead" % (self.dtype,gen))
161 def __eq__(self, other):
162 return isinstance(other, FracField) and \
163 (self.symbols, self.ngens, self.domain, self.order) == \
164 (other.symbols, other.ngens, other.domain, other.order)
166 def __ne__(self, other):
167 return not self == other
169 def raw_new(self, numer, denom=None):
170 return self.dtype(numer, denom)
171 def new(self, numer, denom=None):
172 if denom is None: denom = self.ring.one
173 numer, denom = numer.cancel(denom)
174 return self.raw_new(numer, denom)
176 def domain_new(self, element):
177 return self.domain.convert(element)
179 def ground_new(self, element):
180 try:
181 return self.new(self.ring.ground_new(element))
182 except CoercionFailed:
183 domain = self.domain
185 if not domain.is_Field and domain.has_assoc_Field:
186 ring = self.ring
187 ground_field = domain.get_field()
188 element = ground_field.convert(element)
189 numer = ring.ground_new(ground_field.numer(element))
190 denom = ring.ground_new(ground_field.denom(element))
191 return self.raw_new(numer, denom)
192 else:
193 raise
195 def field_new(self, element):
196 if isinstance(element, FracElement):
197 if self == element.field:
198 return element
200 if isinstance(self.domain, FractionField) and \
201 self.domain.field == element.field:
202 return self.ground_new(element)
203 elif isinstance(self.domain, PolynomialRing) and \
204 self.domain.ring.to_field() == element.field:
205 return self.ground_new(element)
206 else:
207 raise NotImplementedError("conversion")
208 elif isinstance(element, PolyElement):
209 denom, numer = element.clear_denoms()
211 if isinstance(self.domain, PolynomialRing) and \
212 numer.ring == self.domain.ring:
213 numer = self.ring.ground_new(numer)
214 elif isinstance(self.domain, FractionField) and \
215 numer.ring == self.domain.field.to_ring():
216 numer = self.ring.ground_new(numer)
217 else:
218 numer = numer.set_ring(self.ring)
220 denom = self.ring.ground_new(denom)
221 return self.raw_new(numer, denom)
222 elif isinstance(element, tuple) and len(element) == 2:
223 numer, denom = list(map(self.ring.ring_new, element))
224 return self.new(numer, denom)
225 elif isinstance(element, str):
226 raise NotImplementedError("parsing")
227 elif isinstance(element, Expr):
228 return self.from_expr(element)
229 else:
230 return self.ground_new(element)
232 __call__ = field_new
234 def _rebuild_expr(self, expr, mapping):
235 domain = self.domain
236 powers = tuple((gen, gen.as_base_exp()) for gen in mapping.keys()
237 if gen.is_Pow or isinstance(gen, ExpBase))
239 def _rebuild(expr):
240 generator = mapping.get(expr)
242 if generator is not None:
243 return generator
244 elif expr.is_Add:
245 return reduce(add, list(map(_rebuild, expr.args)))
246 elif expr.is_Mul:
247 return reduce(mul, list(map(_rebuild, expr.args)))
248 elif expr.is_Pow or isinstance(expr, (ExpBase, Exp1)):
249 b, e = expr.as_base_exp()
250 # look for bg**eg whose integer power may be b**e
251 for gen, (bg, eg) in powers:
252 if bg == b and Mod(e, eg) == 0:
253 return mapping.get(gen)**int(e/eg)
254 if e.is_Integer and e is not S.One:
255 return _rebuild(b)**int(e)
256 elif mapping.get(1/expr) is not None:
257 return 1/mapping.get(1/expr)
259 try:
260 return domain.convert(expr)
261 except CoercionFailed:
262 if not domain.is_Field and domain.has_assoc_Field:
263 return domain.get_field().convert(expr)
264 else:
265 raise
267 return _rebuild(expr)
269 def from_expr(self, expr):
270 mapping = dict(list(zip(self.symbols, self.gens)))
272 try:
273 frac = self._rebuild_expr(sympify(expr), mapping)
274 except CoercionFailed:
275 raise ValueError("expected an expression convertible to a rational function in %s, got %s" % (self, expr))
276 else:
277 return self.field_new(frac)
279 def to_domain(self):
280 return FractionField(self)
282 def to_ring(self):
283 from sympy.polys.rings import PolyRing
284 return PolyRing(self.symbols, self.domain, self.order)
286class FracElement(DomainElement, DefaultPrinting, CantSympify):
287 """Element of multivariate distributed rational function field. """
289 def __init__(self, numer, denom=None):
290 if denom is None:
291 denom = self.field.ring.one
292 elif not denom:
293 raise ZeroDivisionError("zero denominator")
295 self.numer = numer
296 self.denom = denom
298 def raw_new(f, numer, denom):
299 return f.__class__(numer, denom)
300 def new(f, numer, denom):
301 return f.raw_new(*numer.cancel(denom))
303 def to_poly(f):
304 if f.denom != 1:
305 raise ValueError("f.denom should be 1")
306 return f.numer
308 def parent(self):
309 return self.field.to_domain()
311 def __getnewargs__(self):
312 return (self.field, self.numer, self.denom)
314 _hash = None
316 def __hash__(self):
317 _hash = self._hash
318 if _hash is None:
319 self._hash = _hash = hash((self.field, self.numer, self.denom))
320 return _hash
322 def copy(self):
323 return self.raw_new(self.numer.copy(), self.denom.copy())
325 def set_field(self, new_field):
326 if self.field == new_field:
327 return self
328 else:
329 new_ring = new_field.ring
330 numer = self.numer.set_ring(new_ring)
331 denom = self.denom.set_ring(new_ring)
332 return new_field.new(numer, denom)
334 def as_expr(self, *symbols):
335 return self.numer.as_expr(*symbols)/self.denom.as_expr(*symbols)
337 def __eq__(f, g):
338 if isinstance(g, FracElement) and f.field == g.field:
339 return f.numer == g.numer and f.denom == g.denom
340 else:
341 return f.numer == g and f.denom == f.field.ring.one
343 def __ne__(f, g):
344 return not f == g
346 def __bool__(f):
347 return bool(f.numer)
349 def sort_key(self):
350 return (self.denom.sort_key(), self.numer.sort_key())
352 def _cmp(f1, f2, op):
353 if isinstance(f2, f1.field.dtype):
354 return op(f1.sort_key(), f2.sort_key())
355 else:
356 return NotImplemented
358 def __lt__(f1, f2):
359 return f1._cmp(f2, lt)
360 def __le__(f1, f2):
361 return f1._cmp(f2, le)
362 def __gt__(f1, f2):
363 return f1._cmp(f2, gt)
364 def __ge__(f1, f2):
365 return f1._cmp(f2, ge)
367 def __pos__(f):
368 """Negate all coefficients in ``f``. """
369 return f.raw_new(f.numer, f.denom)
371 def __neg__(f):
372 """Negate all coefficients in ``f``. """
373 return f.raw_new(-f.numer, f.denom)
375 def _extract_ground(self, element):
376 domain = self.field.domain
378 try:
379 element = domain.convert(element)
380 except CoercionFailed:
381 if not domain.is_Field and domain.has_assoc_Field:
382 ground_field = domain.get_field()
384 try:
385 element = ground_field.convert(element)
386 except CoercionFailed:
387 pass
388 else:
389 return -1, ground_field.numer(element), ground_field.denom(element)
391 return 0, None, None
392 else:
393 return 1, element, None
395 def __add__(f, g):
396 """Add rational functions ``f`` and ``g``. """
397 field = f.field
399 if not g:
400 return f
401 elif not f:
402 return g
403 elif isinstance(g, field.dtype):
404 if f.denom == g.denom:
405 return f.new(f.numer + g.numer, f.denom)
406 else:
407 return f.new(f.numer*g.denom + f.denom*g.numer, f.denom*g.denom)
408 elif isinstance(g, field.ring.dtype):
409 return f.new(f.numer + f.denom*g, f.denom)
410 else:
411 if isinstance(g, FracElement):
412 if isinstance(field.domain, FractionField) and field.domain.field == g.field:
413 pass
414 elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
415 return g.__radd__(f)
416 else:
417 return NotImplemented
418 elif isinstance(g, PolyElement):
419 if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
420 pass
421 else:
422 return g.__radd__(f)
424 return f.__radd__(g)
426 def __radd__(f, c):
427 if isinstance(c, f.field.ring.dtype):
428 return f.new(f.numer + f.denom*c, f.denom)
430 op, g_numer, g_denom = f._extract_ground(c)
432 if op == 1:
433 return f.new(f.numer + f.denom*g_numer, f.denom)
434 elif not op:
435 return NotImplemented
436 else:
437 return f.new(f.numer*g_denom + f.denom*g_numer, f.denom*g_denom)
439 def __sub__(f, g):
440 """Subtract rational functions ``f`` and ``g``. """
441 field = f.field
443 if not g:
444 return f
445 elif not f:
446 return -g
447 elif isinstance(g, field.dtype):
448 if f.denom == g.denom:
449 return f.new(f.numer - g.numer, f.denom)
450 else:
451 return f.new(f.numer*g.denom - f.denom*g.numer, f.denom*g.denom)
452 elif isinstance(g, field.ring.dtype):
453 return f.new(f.numer - f.denom*g, f.denom)
454 else:
455 if isinstance(g, FracElement):
456 if isinstance(field.domain, FractionField) and field.domain.field == g.field:
457 pass
458 elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
459 return g.__rsub__(f)
460 else:
461 return NotImplemented
462 elif isinstance(g, PolyElement):
463 if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
464 pass
465 else:
466 return g.__rsub__(f)
468 op, g_numer, g_denom = f._extract_ground(g)
470 if op == 1:
471 return f.new(f.numer - f.denom*g_numer, f.denom)
472 elif not op:
473 return NotImplemented
474 else:
475 return f.new(f.numer*g_denom - f.denom*g_numer, f.denom*g_denom)
477 def __rsub__(f, c):
478 if isinstance(c, f.field.ring.dtype):
479 return f.new(-f.numer + f.denom*c, f.denom)
481 op, g_numer, g_denom = f._extract_ground(c)
483 if op == 1:
484 return f.new(-f.numer + f.denom*g_numer, f.denom)
485 elif not op:
486 return NotImplemented
487 else:
488 return f.new(-f.numer*g_denom + f.denom*g_numer, f.denom*g_denom)
490 def __mul__(f, g):
491 """Multiply rational functions ``f`` and ``g``. """
492 field = f.field
494 if not f or not g:
495 return field.zero
496 elif isinstance(g, field.dtype):
497 return f.new(f.numer*g.numer, f.denom*g.denom)
498 elif isinstance(g, field.ring.dtype):
499 return f.new(f.numer*g, f.denom)
500 else:
501 if isinstance(g, FracElement):
502 if isinstance(field.domain, FractionField) and field.domain.field == g.field:
503 pass
504 elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
505 return g.__rmul__(f)
506 else:
507 return NotImplemented
508 elif isinstance(g, PolyElement):
509 if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
510 pass
511 else:
512 return g.__rmul__(f)
514 return f.__rmul__(g)
516 def __rmul__(f, c):
517 if isinstance(c, f.field.ring.dtype):
518 return f.new(f.numer*c, f.denom)
520 op, g_numer, g_denom = f._extract_ground(c)
522 if op == 1:
523 return f.new(f.numer*g_numer, f.denom)
524 elif not op:
525 return NotImplemented
526 else:
527 return f.new(f.numer*g_numer, f.denom*g_denom)
529 def __truediv__(f, g):
530 """Computes quotient of fractions ``f`` and ``g``. """
531 field = f.field
533 if not g:
534 raise ZeroDivisionError
535 elif isinstance(g, field.dtype):
536 return f.new(f.numer*g.denom, f.denom*g.numer)
537 elif isinstance(g, field.ring.dtype):
538 return f.new(f.numer, f.denom*g)
539 else:
540 if isinstance(g, FracElement):
541 if isinstance(field.domain, FractionField) and field.domain.field == g.field:
542 pass
543 elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
544 return g.__rtruediv__(f)
545 else:
546 return NotImplemented
547 elif isinstance(g, PolyElement):
548 if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
549 pass
550 else:
551 return g.__rtruediv__(f)
553 op, g_numer, g_denom = f._extract_ground(g)
555 if op == 1:
556 return f.new(f.numer, f.denom*g_numer)
557 elif not op:
558 return NotImplemented
559 else:
560 return f.new(f.numer*g_denom, f.denom*g_numer)
562 def __rtruediv__(f, c):
563 if not f:
564 raise ZeroDivisionError
565 elif isinstance(c, f.field.ring.dtype):
566 return f.new(f.denom*c, f.numer)
568 op, g_numer, g_denom = f._extract_ground(c)
570 if op == 1:
571 return f.new(f.denom*g_numer, f.numer)
572 elif not op:
573 return NotImplemented
574 else:
575 return f.new(f.denom*g_numer, f.numer*g_denom)
577 def __pow__(f, n):
578 """Raise ``f`` to a non-negative power ``n``. """
579 if n >= 0:
580 return f.raw_new(f.numer**n, f.denom**n)
581 elif not f:
582 raise ZeroDivisionError
583 else:
584 return f.raw_new(f.denom**-n, f.numer**-n)
586 def diff(f, x):
587 """Computes partial derivative in ``x``.
589 Examples
590 ========
592 >>> from sympy.polys.fields import field
593 >>> from sympy.polys.domains import ZZ
595 >>> _, x, y, z = field("x,y,z", ZZ)
596 >>> ((x**2 + y)/(z + 1)).diff(x)
597 2*x/(z + 1)
599 """
600 x = x.to_poly()
601 return f.new(f.numer.diff(x)*f.denom - f.numer*f.denom.diff(x), f.denom**2)
603 def __call__(f, *values):
604 if 0 < len(values) <= f.field.ngens:
605 return f.evaluate(list(zip(f.field.gens, values)))
606 else:
607 raise ValueError("expected at least 1 and at most %s values, got %s" % (f.field.ngens, len(values)))
609 def evaluate(f, x, a=None):
610 if isinstance(x, list) and a is None:
611 x = [ (X.to_poly(), a) for X, a in x ]
612 numer, denom = f.numer.evaluate(x), f.denom.evaluate(x)
613 else:
614 x = x.to_poly()
615 numer, denom = f.numer.evaluate(x, a), f.denom.evaluate(x, a)
617 field = numer.ring.to_field()
618 return field.new(numer, denom)
620 def subs(f, x, a=None):
621 if isinstance(x, list) and a is None:
622 x = [ (X.to_poly(), a) for X, a in x ]
623 numer, denom = f.numer.subs(x), f.denom.subs(x)
624 else:
625 x = x.to_poly()
626 numer, denom = f.numer.subs(x, a), f.denom.subs(x, a)
628 return f.new(numer, denom)
630 def compose(f, x, a=None):
631 raise NotImplementedError