Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
sagemathgh-37143: special subs for fractions
    
We create a specialised method `subs` for `FractionFieldElements`, for
better performance.  In particular, the generic `Element.subs` method
insists on replacing all gens, which is a problem in polynomial rings
with many variables, such as (potentially) the `InfinitePolynomialRing`.

Fixes sagemath#37122.
    
URL: sagemath#37143
Reported by: Martin Rubey
Reviewer(s): Martin Rubey, Travis Scrimshaw
  • Loading branch information
Release Manager committed Feb 11, 2024
2 parents 53c3468 + 3bd05ab commit dd9abe4
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/sage/rings/fraction_field_FpT.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ cdef class FpTElement(FieldElement):
"""
return self.numer()(*args, **kwds) / self.denom()(*args, **kwds)

def subs(self, *args, **kwds):
def subs(self, in_dict=None, *args, **kwds):
"""
EXAMPLES::
Expand All @@ -280,7 +280,7 @@ cdef class FpTElement(FieldElement):
sage: f.subs(X=2)
(t + 1)/(t + 10)
"""
return self.numer().subs(*args, **kwds) / self.denom().subs(*args, **kwds)
return self.numer().subs(in_dict, *args, **kwds) / self.denom().subs(in_dict, *args, **kwds)

def valuation(self, v):
"""
Expand Down
38 changes: 38 additions & 0 deletions src/sage/rings/fraction_field_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from sage.structure.element cimport FieldElement, parent
from sage.structure.richcmp cimport richcmp

from sage.rings.rational_field import QQ
from sage.rings.integer_ring import ZZ

import sage.misc.latex as latex

Expand Down Expand Up @@ -446,6 +447,43 @@ cdef class FractionFieldElement(FieldElement):
"""
return self._numerator(*x, **kwds) / self._denominator(*x, **kwds)

def subs(self, in_dict=None, *args, **kwds):
r"""
Substitute variables in the numerator and denominator of ``self``.
If a dictionary is passed, the keys are mapped to generators
of the parent ring. Otherwise, the arguments are transmitted
unchanged to the method ``subs`` of the numerator and the
denominator.
EXAMPLES::
sage: x, y = PolynomialRing(ZZ, 2, 'xy').gens()
sage: f = x^2 + y + x^2*y^2 + 5
sage: (1/f).subs(x=5)
1/(25*y^2 + y + 30)
TESTS:
Check that :issue:`37122` is fixed::
sage: P = PolynomialRing(QQ, ["x%s" % i for i in range(10000)])
sage: PF = P.fraction_field()
sage: p = sum(i*P.gen(i) for i in range(5)) / sum(i*P.gen(i) for i in range(8))
sage: v = P.gen(4)
sage: p.subs({v: 100})
(x1 + 2*x2 + 3*x3 + 400)/(x1 + 2*x2 + 3*x3 + 5*x5 + 6*x6 + 7*x7 + 400)
"""
if isinstance(in_dict, dict):
R = self.parent().base()
in_dict = {ZZ(m) if m in ZZ else R(m): v for m, v in in_dict.items()}

num = self._numerator.subs(in_dict, *args, **kwds)
den = self._denominator.subs(in_dict, *args, **kwds)
return num / den

substitute = subs

def _is_atomic(self):
"""
EXAMPLES::
Expand Down
10 changes: 8 additions & 2 deletions src/sage/rings/polynomial/laurent_polynomial_mpair.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1345,14 +1345,20 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial):
sage: x.subs({x: 2}, x=1)
1
sage: f.subs({1: 2}, x=1)
3*z + 5
"""
cdef list variables = list(self._parent.gens())
cdef Py_ssize_t i
for i in range(len(variables)):
if str(variables[i]) in kwds:
variables[i] = kwds[str(variables[i])]
elif in_dict and variables[i] in in_dict:
variables[i] = in_dict[variables[i]]
elif in_dict:
if variables[i] in in_dict:
variables[i] = in_dict[variables[i]]
elif i in in_dict:
variables[i] = in_dict[i]
return self(tuple(variables))

def is_constant(self):
Expand Down
22 changes: 12 additions & 10 deletions src/sage/rings/polynomial/multi_polynomial_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ def __call__(self, *x, **kwds):
except AttributeError:
K = self.parent().base_ring()
y = K(0)
for (m,c) in self.element().dict().items():
y += c*prod([ x[i]**m[i] for i in range(n) if m[i] != 0])
for m, c in self.element().dict().items():
y += c * prod(v ** e for v, e in zip(x, m) if e)
return y

def _richcmp_(self, right, op):
Expand Down Expand Up @@ -1388,7 +1388,7 @@ def is_term(self):
"""
return len(self.element()) == 1

def subs(self, fixed=None, **kw):
def subs(self, fixed=None, **kwds):
"""
Fix some given variables in a given multivariate polynomial and
return the changed multivariate polynomials. The polynomial itself
Expand All @@ -1400,10 +1400,9 @@ def subs(self, fixed=None, **kw):
INPUT:
- ``fixed`` - (optional) dictionary of inputs
- ``**kw`` - named parameters
- ``**kwds`` - named parameters
OUTPUT: new :class:`MPolynomial`
Expand All @@ -1419,11 +1418,14 @@ def subs(self, fixed=None, **kw):
25*y^2 + y + 30
"""
variables = list(self.parent().gens())
for i in range(0,len(variables)):
if str(variables[i]) in kw:
variables[i] = kw[str(variables[i])]
elif fixed and variables[i] in fixed:
variables[i] = fixed[variables[i]]
for i in range(len(variables)):
if str(variables[i]) in kwds:
variables[i] = kwds[str(variables[i])]
elif fixed:
if variables[i] in fixed:
variables[i] = fixed[variables[i]]
elif i in fixed:
variables[i] = fixed[i]
return self(tuple(variables))

def monomials(self):
Expand Down
27 changes: 15 additions & 12 deletions src/sage/rings/polynomial/polynomial_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -411,11 +411,9 @@ cdef class Polynomial(CommutativePolynomial):
return self._parent.zero()
return self * self._parent(right)

def subs(self, *x, **kwds):
def subs(self, in_dict=None, *args, **kwds):
r"""
Identical to ``self(*x)``.
See the docstring for :meth:`__call__`.
Substitute the variable in ``self``.
EXAMPLES::
Expand All @@ -434,14 +432,19 @@ cdef class Polynomial(CommutativePolynomial):
...
TypeError: keys do not match self's parent
"""
if len(x) == 1 and isinstance(x[0], dict):
g = self._parent.gen()
if g in x[0]:
return self(x[0][g])
elif len(x[0]) > 0:
raise TypeError("keys do not match self's parent")
return self
return self(*x, **kwds)
if not in_dict:
return self(*args, **kwds)

if isinstance(in_dict, dict):
if len(in_dict) > 1:
raise TypeError("only the generator can be substituted, use __call__ instead")
k, v = next(iter(in_dict.items()))
if not k or k == self._parent.gen():
return self(v, *args, **kwds)
raise TypeError("keys do not match self's parent")

return self(in_dict, *args, **kwds)

substitute = subs

@cython.boundscheck(False)
Expand Down

0 comments on commit dd9abe4

Please sign in to comment.