Skip to content

Commit

Permalink
gh-38607: Add subs method for function field elements
Browse files Browse the repository at this point in the history
    
Fixes #38514.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

None
    
URL: #38607
Reported by: Vincent Macri
Reviewer(s): Travis Scrimshaw, Vincent Macri
  • Loading branch information
Release Manager committed Sep 22, 2024
2 parents 7c6c1af + 2066c0b commit 29d7056
Showing 1 changed file with 260 additions and 3 deletions.
263 changes: 260 additions & 3 deletions src/sage/rings/function_field/element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ AUTHORS:
- Maarten Derickx (2011-09-11): added doctests, fixed pickling
- Kwankyu Lee (2017-04-30): added elements for global function fields
- Vincent Macri (2024-09-03): added subs method
"""
# *****************************************************************************
# Copyright (C) 2010 William Stein <[email protected]>
Expand All @@ -57,6 +59,7 @@ AUTHORS:
# 2018-2020 Travis Scrimshaw
# 2019 Brent Baccala
# 2021 Saher Amasha
# 2024 Vincent Macri
#
# Distributed under the terms of the GNU General Public License (GPL)
# as published by the Free Software Foundation; either version 2 of
Expand Down Expand Up @@ -174,6 +177,260 @@ cdef class FunctionFieldElement(FieldElement):
"""
return self._x._latex_()

def subs(self, in_dict=None, **kwds):
r"""
Substitute the given generators with given values while not touching
other generators.
INPUT:
- ``in_dict`` -- (optional) dictionary of inputs
- ``**kwds`` -- named parameters
OUTPUT: new object if substitution is possible, otherwise ``self``
EXAMPLES:
Basic substitution::
sage: K = GF(7)
sage: Kx.<x> = FunctionField(K)
sage: y = polygen(Kx)
sage: f = x^6 + 3; f
x^6 + 3
We also substitute the generators in any base fields::
sage: K.<x> = FunctionField(QQ)
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^3 - (x^3 + 2*x*y + 1/x))
sage: S.<t> = L[]
sage: M.<t> = L.extension(t^2 - x*y)
sage: f = 7 * t + 3*x*y
sage: f.subs(t=9)
3*x*y + 63
sage: f.subs(x=2, y=4)
7*t + 24
sage: f.subs(t=1, x=2, y=3)
25
Because of the possibility of extension fields, a generator to
substitute must be specified::
sage: K.<x> = FunctionField(QQ)
sage: f = x
sage: f.subs(2)
Traceback (most recent call last):
...
TypeError: in_dict must be a dict
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^3 - (x^3 + 2*x*y + 1/x))
sage: f = x + y
sage: f.subs(0)
Traceback (most recent call last):
...
TypeError: in_dict must be a dict
We can also substitute using dictionary syntax::
sage: K.<x> = FunctionField(QQ)
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^3 - (x^3 + 2*x*y + 1/x))
sage: S.<t> = L[]
sage: M.<t> = L.extension(t^2 - x*y)
sage: f = x + y + t
sage: f.subs({x: 1, y: 3, t: 4})
8
sage: f.subs({x: 1, t: 4})
y + 5
TESTS:
Check that we correctly handle extension fields::
sage: K.<x> = FunctionField(QQ)
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^3 - (x^3 + 2*x*y + 1/x))
sage: S.<t> = L[]
sage: M.<t> = L.extension(t^2 - x*y)
sage: f = t + x*y
sage: f.subs(x=1, y=3, t=5)
8
sage: f_sub = f.subs(x=1); f_sub
t + y
sage: f_sub.parent() == f.parent()
True
sage: f.subs(y=2)
t + 2*x
sage: f_sub = f.subs(x=1, y=1, t=1); f_sub
2
sage: f_sub.parent() == M
True
Test that substitution works for rational functions::
sage: K.<x> = FunctionField(QQ)
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^4 - 3)
sage: f = x / y
sage: f.subs(x=2) == 2 / y
True
sage: f.subs(y=3)
9*x
sage: f.subs(t=-1) is f
True
sage: f.subs({x: 2, y: 4})
128/3
Make sure that we return the same object when there is no
substitution::
sage: K = GF(7)
sage: Kx.<x> = FunctionField(K)
sage: y = polygen(Kx)
sage: f = x^6 + 3
sage: g = f.subs(z=2)
sage: g == f
True
sage: g is f
True
Same purpose as above but over an extension field over the rationals::
sage: K.<x> = FunctionField(QQ)
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^3 - (x^3 + 2*x*y + 1/x))
sage: S.<t> = L[]
sage: M.<t> = L.extension(t^2 - x*y)
sage: f = t + x*y
sage: f.subs() is f
True
sage: f.subs(w=7) is f
True
sage: f.subs(w=7) is f.subs(w=7)
True
sage: f.subs(y=y) is f
True
sage: f.subs({y: y}) is f
True
sage: f.subs(x=x, y=y, t=t) is f
True
Test proper handling of not making substitutions::
sage: K.<x> = FunctionField(QQ)
sage: f = x
sage: f.subs() is f
True
sage: f.subs(dict()) is f
True
sage: f.subs(w=0) is f
True
sage: R.<y> = K[]
sage: L.<y> = K.extension(y^3 - (x^3 + 2*x*y + 1/x))
sage: f = 3*y
sage: f.subs(x=0)
3*y
sage: f = 3*y
sage: f.subs(x=0, y=y)
3*y
Test error handling for wrong argument type::
sage: K.<x> = FunctionField(QQ)
sage: f = x
sage: f.subs(0)
Traceback (most recent call last):
...
TypeError: in_dict must be a dict
Test error handling for dictionary with keys that don't match
generators::
sage: K.<x> = FunctionField(QQ)
sage: f = x
sage: f.subs({1: 1})
Traceback (most recent call last):
...
TypeError: key does not match any field generators
Test error handling with ambiguously named generators::
sage: K.<x> = FunctionField(QQ)
sage: R.<x> = K[]
sage: L.<x> = K.extension(x^3 - x)
sage: str(L.gen()) == str(K.gen())
True
sage: f = K.gen() - L.gen()
sage: f.subs(x=2)
Traceback (most recent call last):
...
TypeError: multiple generators have the same name, making substitution ambiguous. Rename generators or pass substitution values in using dictionary format
sage: f.subs({K.gen(): 1})
-x + 1
sage: f.subs({L.gen(): 2})
x - 2
sage: f.subs({K.gen(): 1, L.gen(): 2})
-1
sage: f.subs({K.gen(): 2, L.gen(): 1})
1
"""
def sub_recurse(ff_element, sub_dict):
# Helper method to recurse through base fields.
ff = ff_element.parent()
if ff.base_field() == ff:
return ff(ff_element._x.subs({ff.gen(): sub_dict[ff.gen()]}))
total = ff.zero()
for i, v in enumerate(list(ff_element._x)):
total += sub_recurse(v, sub_dict) * sub_dict[ff.gen()]**i
return ff(total)

if in_dict is None and kwds is None:
return self

if in_dict is not None and not isinstance(in_dict, dict):
raise TypeError('in_dict must be a dict')

field_tower = [self.parent()]
ff = self.parent()

while ff.base_field() != ff:
ff = ff.base_field()
field_tower.append(ff)
sub_dict = {f.gen(): f.gen() for f in field_tower}

made_substitution = False
if in_dict is not None:
for k, v in in_dict.items():
if k not in sub_dict:
raise TypeError('key does not match any field generators')
sub_dict[k] = v
if v != k:
made_substitution = True
else:
used_kwds = {k: False for k in kwds}
for g in sub_dict:
strg = str(g)
if strg not in kwds:
continue
v = kwds[strg]
sub_dict[g] = v

if used_kwds[strg]:
raise TypeError('multiple generators have the '
'same name, making substitution '
'ambiguous. Rename generators '
'or pass substitution values in '
'using dictionary format')
used_kwds[strg] = True
if g != v:
made_substitution = True

if made_substitution:
return sub_recurse(self, sub_dict)
return self

@cached_method
def matrix(self, base=None):
r"""
Expand Down Expand Up @@ -248,7 +505,7 @@ cdef class FunctionFieldElement(FieldElement):
# with this element; make matrix whose rows are the coefficients of the
# result, and transpose
V, f, t = self.parent().vector_space(base)
rows = [ t(self*f(b)) for b in V.basis() ]
rows = [t(self*f(b)) for b in V.basis()]
from sage.matrix.matrix_space import MatrixSpace
MS = MatrixSpace(V.base_field(), V.dimension())
ret = MS(rows)
Expand Down Expand Up @@ -326,7 +583,7 @@ cdef class FunctionFieldElement(FieldElement):
sage: f.degree()
1
"""
return max(self._x.denominator().degree(),self._x.numerator().degree())
return max(self._x.denominator().degree(), self._x.numerator().degree())

def characteristic_polynomial(self, *args, **kwds):
"""
Expand Down Expand Up @@ -681,7 +938,7 @@ cdef class FunctionFieldElement(FieldElement):
v = self.valuation(place)
if v > 0:
return R.zero()
if v == 0:
if v == 0:
return to_R(self)
# v < 0
raise ValueError('has a pole at the place')
Expand Down

0 comments on commit 29d7056

Please sign in to comment.