-
-
Notifications
You must be signed in to change notification settings - Fork 523
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gh-38607: Add subs method for function field elements
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
Showing
1 changed file
with
260 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]> | ||
|
@@ -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 | ||
|
@@ -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""" | ||
|
@@ -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) | ||
|
@@ -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): | ||
""" | ||
|
@@ -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') | ||
|