Skip to content

Commit c7be323

Browse files
author
Release Manager
committed
Trac #34791: move ProductTree and prod_with_derivative() to sage.rings.generic
The class `ProductTree` and the function `prod_with_derivative()` were introduced in #34303. Both are fully generic in principle, but they remained in `hom_velusqrt.py` in the heat of the moment. We should move them to a more suitable place; it seems `sage.rings.generic` is an appropriate choice. Slight tweaks to `ProductTree` while we're at it. URL: https://trac.sagemath.org/34791 Reported by: lorenz Ticket author(s): Lorenz Panny Reviewer(s): Kwankyu Lee
2 parents 814d948 + c3dcb9b commit c7be323

File tree

3 files changed

+269
-193
lines changed

3 files changed

+269
-193
lines changed

src/doc/en/reference/rings/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ Ring Extensions
6565
sage/rings/ring_extension_morphism
6666

6767

68+
Generic Data Structures and Algorithms for Rings
69+
------------------------------------------------
70+
71+
.. toctree::
72+
:maxdepth: 1
73+
74+
sage/rings/generic
75+
6876
Utilities
6977
---------
7078

src/sage/rings/generic.py

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
r"""
2+
Generic data structures and algorithms for rings
3+
4+
AUTHORS:
5+
6+
- Lorenz Panny (2022): :class:`ProductTree`, :func:`prod_with_derivative`
7+
"""
8+
9+
from sage.misc.misc_c import prod
10+
11+
12+
class ProductTree:
13+
r"""
14+
A simple binary product tree, i.e., a tree of ring elements in
15+
which every node equals the product of its children.
16+
(In particular, the *root* equals the product of all *leaves*.)
17+
18+
Product trees are a very useful building block for fast computer
19+
algebra. For example, a quasilinear-time Discrete Fourier Transform
20+
(the famous *Fast* Fourier Transform) can be implemented as follows
21+
using the :meth:`remainders` method of this class::
22+
23+
sage: from sage.rings.generic import ProductTree
24+
sage: F = GF(65537)
25+
sage: a = F(1111)
26+
sage: assert a.multiplicative_order() == 1024
27+
sage: R.<x> = F[]
28+
sage: ms = [x - a^i for i in range(1024)] # roots of unity
29+
sage: ys = [F.random_element() for _ in range(1024)] # input vector
30+
sage: zs = ProductTree(ms).remainders(R(ys)) # compute FFT!
31+
sage: zs == [R(ys) % m for m in ms]
32+
True
33+
34+
This class encodes the tree as *layers*: Layer `0` is just a tuple
35+
of the leaves. Layer `i+1` is obtained from layer `i` by replacing
36+
each pair of two adjacent elements by their product, starting from
37+
the left. (If the length is odd, the unpaired element at the end is
38+
simply copied as is.) This iteration stops as soon as it yields a
39+
layer containing only a single element (the root).
40+
41+
.. NOTE::
42+
43+
Use this class if you need the :meth:`remainders` method.
44+
To compute just the product, :func:`prod` is likely faster.
45+
46+
INPUT:
47+
48+
- ``leaves`` -- an iterable of elements in a common ring
49+
50+
EXAMPLES::
51+
52+
sage: from sage.rings.generic import ProductTree
53+
sage: R.<x> = GF(101)[]
54+
sage: vs = [x - i for i in range(1,10)]
55+
sage: tree = ProductTree(vs)
56+
sage: tree.root()
57+
x^9 + 56*x^8 + 62*x^7 + 44*x^6 + 47*x^5 + 42*x^4 + 15*x^3 + 11*x^2 + 12*x + 13
58+
sage: tree.remainders(x^7 + x + 1)
59+
[3, 30, 70, 27, 58, 72, 98, 98, 23]
60+
sage: tree.remainders(x^100)
61+
[1, 1, 1, 1, 1, 1, 1, 1, 1]
62+
63+
::
64+
65+
sage: vs = prime_range(100)
66+
sage: tree = ProductTree(vs)
67+
sage: tree.root().factor()
68+
2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71 * 73 * 79 * 83 * 89 * 97
69+
sage: tree.remainders(3599)
70+
[1, 2, 4, 1, 2, 11, 12, 8, 11, 3, 3, 10, 32, 30, 27, 48, 0, 0, 48, 49, 22, 44, 30, 39, 10]
71+
72+
We can access the individual layers of the tree::
73+
74+
sage: tree.layers
75+
[(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97),
76+
(6, 35, 143, 323, 667, 1147, 1763, 2491, 3599, 4757, 5767, 7387, 97),
77+
(210, 46189, 765049, 4391633, 17120443, 42600829, 97),
78+
(9699690, 3359814435017, 729345064647247, 97),
79+
(32589158477190044730, 70746471270782959),
80+
(2305567963945518424753102147331756070,)]
81+
"""
82+
def __init__(self, leaves):
83+
r"""
84+
Initialize a product tree having the given ring elements
85+
as its leaves.
86+
87+
EXAMPLES::
88+
89+
sage: from sage.rings.generic import ProductTree
90+
sage: vs = prime_range(100)
91+
sage: tree = ProductTree(vs)
92+
"""
93+
V = tuple(leaves)
94+
self.layers = [V]
95+
while len(V) > 1:
96+
V = tuple(prod(V[i:i+2]) for i in range(0,len(V),2))
97+
self.layers.append(V)
98+
99+
def __len__(self):
100+
r"""
101+
Return the number of leaves of this product tree.
102+
103+
EXAMPLES::
104+
105+
sage: from sage.rings.generic import ProductTree
106+
sage: R.<x> = GF(101)[]
107+
sage: vs = [x - i for i in range(1,10)]
108+
sage: tree = ProductTree(vs)
109+
sage: len(tree)
110+
9
111+
sage: len(tree) == len(vs)
112+
True
113+
sage: len(tree.remainders(x^2))
114+
9
115+
"""
116+
return len(self.layers[0])
117+
118+
def __iter__(self):
119+
r"""
120+
Return an iterator over the leaves of this product tree.
121+
122+
EXAMPLES::
123+
124+
sage: from sage.rings.generic import ProductTree
125+
sage: R.<x> = GF(101)[]
126+
sage: vs = [x - i for i in range(1,10)]
127+
sage: tree = ProductTree(vs)
128+
sage: next(iter(tree)) == vs[0]
129+
True
130+
sage: list(tree) == vs
131+
True
132+
"""
133+
return iter(self.layers[0])
134+
135+
def root(self):
136+
r"""
137+
Return the value at the root of this product tree (i.e., the product of all leaves).
138+
139+
EXAMPLES::
140+
141+
sage: from sage.rings.generic import ProductTree
142+
sage: R.<x> = GF(101)[]
143+
sage: vs = [x - i for i in range(1,10)]
144+
sage: tree = ProductTree(vs)
145+
sage: tree.root()
146+
x^9 + 56*x^8 + 62*x^7 + 44*x^6 + 47*x^5 + 42*x^4 + 15*x^3 + 11*x^2 + 12*x + 13
147+
sage: tree.root() == prod(vs)
148+
True
149+
"""
150+
assert len(self.layers[-1]) == 1
151+
return self.layers[-1][0]
152+
153+
def leaves(self):
154+
r"""
155+
Return a tuple containing the leaves of this product tree.
156+
157+
EXAMPLES::
158+
159+
sage: from sage.rings.generic import ProductTree
160+
sage: R.<x> = GF(101)[]
161+
sage: vs = [x - i for i in range(1,10)]
162+
sage: tree = ProductTree(vs)
163+
sage: tree.leaves()
164+
(x + 100, x + 99, x + 98, ..., x + 93, x + 92)
165+
sage: tree.leaves() == tuple(vs)
166+
True
167+
"""
168+
return self.layers[0]
169+
170+
def remainders(self, x):
171+
r"""
172+
Given a value `x`, return a list of all remainders of `x`
173+
modulo the leaves of this product tree.
174+
175+
The base ring must support the ``%`` operator for this
176+
method to work.
177+
178+
179+
INPUT:
180+
181+
- ``x`` -- an element of the base ring of this product tree
182+
183+
EXAMPLES::
184+
185+
sage: from sage.rings.generic import ProductTree
186+
sage: vs = prime_range(100)
187+
sage: tree = ProductTree(vs)
188+
sage: n = 1085749272377676749812331719267
189+
sage: tree.remainders(n)
190+
[1, 1, 2, 1, 9, 1, 7, 15, 8, 20, 15, 6, 27, 11, 2, 6, 0, 25, 49, 5, 51, 4, 19, 74, 13]
191+
sage: [n % v for v in vs]
192+
[1, 1, 2, 1, 9, 1, 7, 15, 8, 20, 15, 6, 27, 11, 2, 6, 0, 25, 49, 5, 51, 4, 19, 74, 13]
193+
"""
194+
X = [x]
195+
for V in reversed(self.layers):
196+
X = [X[i // 2] % V[i] for i in range(len(V))]
197+
return X
198+
199+
200+
def prod_with_derivative(pairs):
201+
r"""
202+
Given an iterable of pairs `(f, \partial f)` of ring elements,
203+
return the pair `(\prod f, \partial \prod f)`, assuming `\partial`
204+
is an operator obeying the standard product rule.
205+
206+
This function is entirely algebraic, hence still works when the
207+
elements `f` and `\partial f` are all passed through some ring
208+
homomorphism first. One particularly useful instance of this is
209+
evaluating the derivative of a product of polynomials at a point
210+
without fully expanding the product; see the second example below.
211+
212+
INPUT:
213+
214+
- ``pairs`` -- an iterable of tuples `(f, \partial f)` of elements
215+
of a common ring
216+
217+
ALGORITHM: Repeated application of the product rule.
218+
219+
EXAMPLES::
220+
221+
sage: from sage.rings.generic import prod_with_derivative
222+
sage: R.<x> = ZZ[]
223+
sage: fs = [x^2 + 2*x + 3, 4*x + 5, 6*x^7 + 8*x + 9]
224+
sage: prod(fs)
225+
24*x^10 + 78*x^9 + 132*x^8 + 90*x^7 + 32*x^4 + 140*x^3 + 293*x^2 + 318*x + 135
226+
sage: prod(fs).derivative()
227+
240*x^9 + 702*x^8 + 1056*x^7 + 630*x^6 + 128*x^3 + 420*x^2 + 586*x + 318
228+
sage: F, dF = prod_with_derivative((f, f.derivative()) for f in fs)
229+
sage: F
230+
24*x^10 + 78*x^9 + 132*x^8 + 90*x^7 + 32*x^4 + 140*x^3 + 293*x^2 + 318*x + 135
231+
sage: dF
232+
240*x^9 + 702*x^8 + 1056*x^7 + 630*x^6 + 128*x^3 + 420*x^2 + 586*x + 318
233+
234+
The main reason for this function to exist is that it allows us to
235+
*evaluate* the derivative of a product of polynomials at a point
236+
`\alpha` without ever fully expanding the product *as a polynomial*::
237+
238+
sage: alpha = 42
239+
sage: F(alpha)
240+
442943981574522759
241+
sage: dF(alpha)
242+
104645261461514994
243+
sage: us = [f(alpha) for f in fs]
244+
sage: vs = [f.derivative()(alpha) for f in fs]
245+
sage: prod_with_derivative(zip(us, vs))
246+
(442943981574522759, 104645261461514994)
247+
"""
248+
class _aux:
249+
def __init__(self, f, df):
250+
self.f, self.df = f, df
251+
252+
def __mul__(self, other):
253+
return _aux(self.f * other.f, self.df * other.f + self.f * other.df)
254+
255+
def __iter__(self):
256+
yield self.f
257+
yield self.df
258+
259+
return tuple(prod(_aux(*tup) for tup in pairs))
260+

0 commit comments

Comments
 (0)