Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subs method for function field elements #38607

Merged
merged 16 commits into from
Sep 22, 2024
Merged
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 <wstein@gmail.com>
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
Loading