|
| 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