Skip to content

Commit 574d591

Browse files
committed
move ProductTree and prod_with_derivative() to sage.arith.misc
1 parent 84f02af commit 574d591

File tree

2 files changed

+194
-193
lines changed

2 files changed

+194
-193
lines changed

src/sage/arith/misc.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
AUTHORS:
66
77
- Kevin Stueve (2010-01-17): in ``is_prime(n)``, delegated calculation to ``n.is_prime()``
8+
- Lorenz Panny (2022): :class:`ProductTree`, :func:`prod_with_derivative`
89
"""
910

1011
# ****************************************************************************
@@ -6285,3 +6286,195 @@ def dedekind_psi(N):
62856286
"""
62866287
N = Integer(N)
62876288
return Integer(N * prod(1 + 1 / p for p in N.prime_divisors()))
6289+
6290+
6291+
class ProductTree:
6292+
r"""
6293+
A simple product tree.
6294+
6295+
INPUT:
6296+
6297+
- ``leaves`` -- a sequence of elements in a common ring
6298+
6299+
EXAMPLES::
6300+
6301+
sage: from sage.arith.misc import ProductTree
6302+
sage: R.<x> = GF(101)[]
6303+
sage: vs = [x - i for i in range(1,10)]
6304+
sage: tree = ProductTree(vs)
6305+
sage: tree.root()
6306+
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
6307+
sage: tree.remainders(x^7 + x + 1)
6308+
[3, 30, 70, 27, 58, 72, 98, 98, 23]
6309+
sage: tree.remainders(x^100)
6310+
[1, 1, 1, 1, 1, 1, 1, 1, 1]
6311+
6312+
::
6313+
6314+
sage: vs = prime_range(100)
6315+
sage: tree = ProductTree(vs)
6316+
sage: tree.root().factor()
6317+
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
6318+
sage: tree.remainders(3599)
6319+
[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]
6320+
6321+
We can access the individual layers of the tree::
6322+
6323+
sage: tree.layers
6324+
[(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),
6325+
(6, 35, 143, 323, 667, 1147, 1763, 2491, 3599, 4757, 5767, 7387, 97),
6326+
(210, 46189, 765049, 4391633, 17120443, 42600829, 97),
6327+
(9699690, 3359814435017, 729345064647247, 97),
6328+
(32589158477190044730, 70746471270782959),
6329+
(2305567963945518424753102147331756070,)]
6330+
6331+
.. NOTE::
6332+
6333+
Use this class if you need the :meth:`remainders` method.
6334+
To compute just the product, :func:`prod` is likely faster.
6335+
"""
6336+
def __init__(self, leaves):
6337+
r"""
6338+
Initialize a product tree having the given ring elements
6339+
as its leaves.
6340+
6341+
EXAMPLES::
6342+
6343+
sage: from sage.arith.misc import ProductTree
6344+
sage: vs = prime_range(100)
6345+
sage: tree = ProductTree(vs)
6346+
"""
6347+
V = tuple(leaves)
6348+
self.layers = [V]
6349+
while len(V) > 1:
6350+
V = tuple(prod(V[i:i+2]) for i in range(0,len(V),2))
6351+
self.layers.append(V)
6352+
6353+
def __len__(self):
6354+
r"""
6355+
Return the number of leaves of this product tree.
6356+
6357+
EXAMPLES::
6358+
6359+
sage: from sage.arith.misc import ProductTree
6360+
sage: R.<x> = GF(101)[]
6361+
sage: vs = [x - i for i in range(1,10)]
6362+
sage: tree = ProductTree(vs)
6363+
sage: len(tree)
6364+
9
6365+
sage: len(tree) == len(vs)
6366+
True
6367+
sage: len(tree.remainders(x^2))
6368+
9
6369+
"""
6370+
return len(self.layers[0])
6371+
6372+
def root(self):
6373+
r"""
6374+
Return the value at the root of this product tree (i.e., the product of all leaves).
6375+
6376+
EXAMPLES::
6377+
6378+
sage: from sage.arith.misc import ProductTree
6379+
sage: R.<x> = GF(101)[]
6380+
sage: vs = [x - i for i in range(1,10)]
6381+
sage: tree = ProductTree(vs)
6382+
sage: tree.root()
6383+
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
6384+
sage: tree.root() == prod(vs)
6385+
True
6386+
"""
6387+
assert len(self.layers[-1]) == 1
6388+
return self.layers[-1][0]
6389+
6390+
def remainders(self, x):
6391+
r"""
6392+
Given a value `x`, return a list of all remainders of `x`
6393+
modulo the leaves of this product tree.
6394+
6395+
The base ring must support the ``%`` operator for this
6396+
method to work.
6397+
6398+
INPUT:
6399+
6400+
- ``x`` -- an element of the base ring of this product tree
6401+
6402+
EXAMPLES::
6403+
6404+
sage: from sage.arith.misc import ProductTree
6405+
sage: vs = prime_range(100)
6406+
sage: tree = ProductTree(vs)
6407+
sage: n = 1085749272377676749812331719267
6408+
sage: tree.remainders(n)
6409+
[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]
6410+
sage: [n % v for v in vs]
6411+
[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]
6412+
"""
6413+
X = [x]
6414+
for V in reversed(self.layers):
6415+
X = [X[i//2] % V[i] for i in range(len(V))]
6416+
return X
6417+
6418+
6419+
def prod_with_derivative(pairs):
6420+
r"""
6421+
Given a list of pairs `(f, \partial f)` of ring elements, return
6422+
the pair `(\prod f, \partial \prod f)`, assuming `\partial` is an
6423+
operator obeying the standard product rule.
6424+
6425+
This function is entirely algebraic, hence still works when the
6426+
elements `f` and `\partial f` are all passed through some ring
6427+
homomorphism first. (See the polynomial-evaluation example below.)
6428+
6429+
INPUT:
6430+
6431+
- ``pairs`` -- a sequence of tuples `(f, \partial f)` of elements
6432+
of a common ring
6433+
6434+
ALGORITHM:
6435+
6436+
This function wraps the given pairs in a thin helper class that
6437+
automatically applies the product rule whenever multiplication
6438+
is invoked, then calls :func:`prod` on the wrapped pairs.
6439+
6440+
EXAMPLES::
6441+
6442+
sage: from sage.arith.misc import prod_with_derivative
6443+
sage: R.<x> = ZZ[]
6444+
sage: fs = [x^2 + 2*x + 3, 4*x + 5, 6*x^7 + 8*x + 9]
6445+
sage: prod(fs)
6446+
24*x^10 + 78*x^9 + 132*x^8 + 90*x^7 + 32*x^4 + 140*x^3 + 293*x^2 + 318*x + 135
6447+
sage: prod(fs).derivative()
6448+
240*x^9 + 702*x^8 + 1056*x^7 + 630*x^6 + 128*x^3 + 420*x^2 + 586*x + 318
6449+
sage: F, dF = prod_with_derivative((f, f.derivative()) for f in fs)
6450+
sage: F
6451+
24*x^10 + 78*x^9 + 132*x^8 + 90*x^7 + 32*x^4 + 140*x^3 + 293*x^2 + 318*x + 135
6452+
sage: dF
6453+
240*x^9 + 702*x^8 + 1056*x^7 + 630*x^6 + 128*x^3 + 420*x^2 + 586*x + 318
6454+
6455+
The main reason for this function to exist is that it allows us to
6456+
*evaluate* the derivative of a product of polynomials at a point
6457+
`\alpha` without ever fully expanding the product *as a polynomial*::
6458+
6459+
sage: alpha = 42
6460+
sage: F(alpha)
6461+
442943981574522759
6462+
sage: dF(alpha)
6463+
104645261461514994
6464+
sage: us = [f(alpha) for f in fs]
6465+
sage: vs = [f.derivative()(alpha) for f in fs]
6466+
sage: prod_with_derivative(zip(us, vs))
6467+
(442943981574522759, 104645261461514994)
6468+
"""
6469+
class _aux:
6470+
def __init__(self, f, df):
6471+
self.f, self.df = f, df
6472+
6473+
def __mul__(self, other):
6474+
return _aux(self.f * other.f, self.df * other.f + self.f * other.df)
6475+
6476+
def __iter__(self):
6477+
yield self.f
6478+
yield self.df
6479+
6480+
return tuple(prod(_aux(*tup) for tup in pairs))

0 commit comments

Comments
 (0)