Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/schemes/hyperelliptic_curves/hypellfrob/hypellfrob.cpp
8823 views
1
/* ============================================================================
2
3
hypellfrob.cpp: main matrix() routine
4
5
This file is part of hypellfrob (version 2.1.1).
6
7
Copyright (C) 2007, 2008, David Harvey
8
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
13
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
GNU General Public License for more details.
18
19
You should have received a copy of the GNU General Public License along
20
with this program; if not, write to the Free Software Foundation, Inc.,
21
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23
============================================================================ */
24
25
26
27
#include "hypellfrob.h"
28
#include "recurrences_ntl.h"
29
#include "recurrences_zn_poly.h"
30
31
32
NTL_CLIENT
33
34
35
namespace hypellfrob {
36
37
38
/*
39
For some reason NTL doesn't have conversions to mat_ZZ....
40
*/
41
void conv(mat_ZZ& x, const mat_ZZ_p& a)
42
{
43
x.SetDims(a.NumRows(), a.NumCols());
44
for (int i = 0; i < a.NumRows(); i++)
45
for (int j = 0; j < a.NumCols(); j++)
46
x[i][j] = rep(a[i][j]);
47
}
48
49
void conv(mat_ZZ& x, const mat_zz_p& a)
50
{
51
x.SetDims(a.NumRows(), a.NumCols());
52
for (int i = 0; i < a.NumRows(); i++)
53
for (int j = 0; j < a.NumCols(); j++)
54
x[i][j] = rep(a[i][j]);
55
}
56
57
inline mat_ZZ to_mat_ZZ(const mat_ZZ_p& a)
58
{ mat_ZZ x; conv(x, a); return x; }
59
60
inline mat_ZZ to_mat_ZZ(const mat_zz_p& a)
61
{ mat_ZZ x; conv(x, a); return x; }
62
63
64
65
/*
66
Let M(x) be the matrix M0 + x*M1; this is a matrix of linear polys in x.
67
Let M(a, b) = M(a + 1) M(a + 2) ... M(b). This function evaluates the products
68
M(a[i], b[i]) for some sequence of intervals
69
a[0] < b[0] <= a[1] < b[1] <= ... <= a[n-1] < b[n-1].
70
71
The intervals are supplied in "target", simply as the list
72
a[0], b[0], a[1], b[1], ...
73
74
There are three possible underlying implementations:
75
* ntl_interval_products (ZZ_p version),
76
* ntl_interval_products (zz_p version),
77
* zn_poly_interval_products.
78
This function is a wrapper which takes ZZ_p input, calls one of the three
79
above implementations depending on the size of the current ZZ_p modulus, and
80
produces ouptut in ZZ_p format.
81
82
If the force_ntl flag is set, it will never use the zn_poly version.
83
84
Note that the zn_poly version occasionally fails; this happens more frequently
85
for smaller p, but is extremely rare for larger p. This wrapper detects this
86
and falls back on the zz_p/ZZ_p versions, which should never fail.
87
88
PRECONDITIONS:
89
Let d = b[n-1] - a[0]. Then 2, 3, ... 1 + floor(sqrt(d)) must all be
90
invertible.
91
92
*/
93
void interval_products_wrapper(vector<mat_ZZ_p>& output,
94
const mat_ZZ_p& M0, const mat_ZZ_p& M1,
95
const vector<ZZ>& target,
96
int force_ntl = 0)
97
{
98
const ZZ& modulus = ZZ_p::modulus();
99
100
if (!force_ntl && modulus <= (1UL << (ULONG_BITS - 1)) - 1)
101
{
102
// Small modulus; let's try using zn_poly if we're allowed.
103
// (we don't allow the full ULONG_BITS bits, because I'm worried I
104
// sometimes use NTL code which requires longs rather than ulongs.)
105
zn_mod_t mod;
106
zn_mod_init(mod, to_ulong(modulus));
107
108
int r = M0.NumRows();
109
110
// convert to zn_mod format
111
vector<vector<ulong> > M0_temp(r, vector<ulong>(r));
112
vector<vector<ulong> > M1_temp(r, vector<ulong>(r));
113
vector<vector<vector<ulong> > > output_temp(target.size()/2, M0_temp);
114
115
for (int x = 0; x < r; x++)
116
for (int y = 0; y < r; y++)
117
{
118
M0_temp[y][x] = to_ulong(rep(M0[y][x]));
119
M1_temp[y][x] = to_ulong(rep(M1[y][x]));
120
}
121
122
int success = zn_poly_interval_products(output_temp, M0_temp, M1_temp,
123
target, mod);
124
zn_mod_clear(mod);
125
126
// note: if we failed, we fall back on the ZZ_p or zz_p versions below
127
if (success)
128
{
129
// convert back to ZZ_p format
130
output.clear();
131
mat_ZZ_p temp;
132
temp.SetDims(r, r);
133
for (int i = 0; i < target.size()/2; i++)
134
{
135
for (int x = 0; x < r; x++)
136
for (int y = 0; y < r; y++)
137
temp[y][x] = output_temp[i][y][x];
138
output.push_back(temp);
139
}
140
141
return;
142
}
143
144
// failed
145
}
146
147
if (!modulus.SinglePrecision())
148
{
149
// Large modulus.... go straight to the ZZ_p version
150
ntl_interval_products
151
<ZZ_p, ZZ_pX, ZZ_pXModulus, vec_ZZ_p, mat_ZZ_p, FFTRep>
152
(output, M0, M1, target);
153
}
154
else
155
{
156
// Modulus is small enough to work at single precision.
157
158
// Save old single-precision modulus and set new modulus
159
zz_pContext context;
160
context.save();
161
zz_p::init(to_long(modulus));
162
163
// Convert input data to single-precision format
164
int dim = M0.NumRows();
165
mat_zz_p M0_sp = to_mat_zz_p(to_mat_ZZ(M0));
166
mat_zz_p M1_sp = to_mat_zz_p(to_mat_ZZ(M1));
167
168
// Run ntl_interval_products at single precision
169
vector<mat_zz_p> output_sp;
170
ntl_interval_products
171
<zz_p, zz_pX, zz_pXModulus, vec_zz_p, mat_zz_p, fftRep>
172
(output_sp, M0_sp, M1_sp, target);
173
174
// convert output back to ZZ_p format
175
output.resize(output_sp.size());
176
for (int i = 0; i < output_sp.size(); i++)
177
output[i] = to_mat_ZZ_p(to_mat_ZZ(output_sp[i]));
178
179
// restore old single-precision modulus
180
context.restore();
181
}
182
}
183
184
185
186
/* ============================================================================
187
188
Main routine for computing Frobenius matrix
189
190
============================================================================ */
191
192
193
/*
194
Given polynomials f and g defined over Z/p^N, this routine computes polynomials
195
a and b such that a*f + b*g = 1 mod p^N, with deg(a) < deg(g) and
196
deg(b) < deg(f).
197
198
PRECONDITIONS:
199
f and g must be relatively prime mod p.
200
The leading coefficients of f and g must be invertible mod p.
201
The current NTL modulus should be p^N, and should be the one used to create
202
the polynomials f and g.
203
204
Returns 1 on success, 0 if f and g are not relatively prime mod p.
205
206
NOTE:
207
NTL can almost do this, but not quite; its xgcd routine is only guaranteed
208
to work if the coefficient ring is a field. If you try it over Z/p^N, it
209
usually works, but sometimes you get unlucky, and it crashes and burns.
210
211
*/
212
int padic_xgcd(ZZ_pX& a, ZZ_pX& b, const ZZ_pX& f, const ZZ_pX& g,
213
const ZZ& p, int N)
214
{
215
ZZ_pContext modulus;
216
modulus.save();
217
218
// =================================================
219
// First do it mod p using NTL's xgcd routine
220
221
// reduce down to Z/p
222
ZZ_p::init(p);
223
ZZ_pX f_red = to_ZZ_pX(to_ZZX(f));
224
ZZ_pX g_red = to_ZZ_pX(to_ZZX(g));
225
226
// do xgcd mod p
227
ZZ_pX a_red, b_red, d_red;
228
XGCD(d_red, a_red, b_red, f_red, g_red);
229
230
// lift results to Z/p^N
231
modulus.restore();
232
a = to_ZZ_pX(to_ZZX(a_red));
233
b = to_ZZ_pX(to_ZZX(b_red));
234
ZZ_pX d = to_ZZ_pX(to_ZZX(d_red));
235
236
if (deg(d) != 0)
237
return 0;
238
239
a /= d;
240
b /= d;
241
242
// =================================================
243
// Now improve the approximation until we have enough precision
244
245
for (int prec = 1; prec < N; prec *= 2)
246
{
247
ZZ_pX h = a*f + b*g - 1;
248
ZZ_pX a_adj = -(h * a) % g;
249
ZZ_pX b_adj = -(h * b) % f;
250
a += a_adj;
251
b += b_adj;
252
}
253
254
return 1;
255
}
256
257
258
259
/*
260
Given a matrix A over Z/p^N that is invertible mod p, this routine computes
261
the inverse B = A^(-1) over Z/p^N.
262
263
PRECONDITIONS:
264
The current NTL modulus should be p^N, and should be the one used to create
265
the matrix A.
266
267
NOTE:
268
It's not clear to me (from the code or the documentation) whether NTL's
269
matrix inversion is guaranteed to work over a non-field. So I implemented
270
this one just in case.
271
272
*/
273
void padic_invert_matrix(mat_ZZ_p& B, const mat_ZZ_p& A, const ZZ& p, int N)
274
{
275
ZZ_pContext modulus;
276
modulus.save();
277
278
int n = A.NumRows();
279
280
// =================================================
281
// First do it mod p using NTL matrix inverse
282
283
// reduce to Z/p
284
ZZ_p::init(p);
285
mat_ZZ_p A_red = to_mat_ZZ_p(to_mat_ZZ(A));
286
287
// invert matrix mod p
288
mat_ZZ_p B_red;
289
inv(B_red, A_red);
290
291
// lift back to Z/p^N
292
modulus.restore();
293
B = to_mat_ZZ_p(to_mat_ZZ(B_red));
294
295
// =================================================
296
// Now improve the approximation until we have enough precision
297
298
mat_ZZ_p two;
299
ident(two, n);
300
two *= 2;
301
302
for (int prec = 1; prec < N; prec *= 2)
303
// if BA = I + error, then
304
// ((2I - BA)B)A = (I - err)(I + err) = I - err^2
305
B = (two - B*A) * B;
306
}
307
308
309
310
/*
311
The main function exported from this module. See hypellfrob.h for information.
312
*/
313
int matrix(mat_ZZ& output, const ZZ& p, int N, const ZZX& Q, int force_ntl)
314
{
315
// check input conditions
316
if (N < 1 || p < 3)
317
return 0;
318
319
if ((deg(Q) < 3) || (deg(Q) % 2 == 0) || (!IsOne(LeadCoeff(Q))))
320
return 0;
321
322
int g = (deg(Q) - 1) / 2;
323
324
if (p <= (2*N - 1) * deg(Q))
325
return 0;
326
327
// Back up the caller's modulus
328
ZZ_pContext modulus_caller;
329
modulus_caller.save();
330
331
// Create moduli for working mod p^N and mod p^(N+1)
332
333
ZZ_pContext modulus0, modulus1;
334
335
ZZ_p::init(power(p, N));
336
modulus0.save();
337
ZZ_p::init(power(p, N+1));
338
modulus1.save();
339
340
// =========================================================================
341
// Compute vertical reduction matrix M_V(t) and denominator D_V(t).
342
343
// If N > 1 we need to do this to precision p^(N+1).
344
// If N = 1 we can get away with precision N, since the last reduction
345
// of length (p-1)/2 doesn't involve any divisions by p.
346
ZZ_pContext modulusV = (N == 1) ? modulus0 : modulus1;
347
modulusV.restore();
348
349
// To compute the vertical reduction matrices, for each 0 <= i < 2g, we need
350
// to find R_i(x) and S_i(x) satisfying x^i = R_i(x) Q(x) + S_i(x) Q'(x).
351
vector<ZZ_pX> R, S;
352
ZZ_pX Qred, Qred_diff, S0, R0;
353
354
conv(Qred, Q);
355
Qred_diff = diff(Qred);
356
int result = padic_xgcd(R0, S0, Qred, Qred_diff, p, N+1);
357
if (!result)
358
{
359
// failure if Q(x) and Q'(x) were not relatively prime mod p
360
modulus_caller.restore();
361
return 0;
362
}
363
364
S.push_back(S0);
365
for (int i = 1; i < 2*g; i++)
366
S.push_back(LeftShift(S.back(), 1) % Qred);
367
368
R.push_back(R0);
369
for (int i = 1; i < 2*g; i++)
370
R.push_back(LeftShift(R.back(), 1) % Qred_diff);
371
372
// Then the (j, i) entry of M_V(t) is given by the x^j coefficient of
373
// (2t-1) R_i(x) + 2 S_i'(x).
374
// We store the constant term of M_V(t) in MV0, and the linear term in MV1.
375
mat_ZZ_p MV0, MV1;
376
MV0.SetDims(2*g, 2*g);
377
MV1.SetDims(2*g, 2*g);
378
for (int i = 0; i < 2*g; i++)
379
for (int j = 0; j < 2*g; j++)
380
{
381
MV0[j][i] = -coeff(R[i], j) + 2*(j+1)*coeff(S[i], j+1);
382
MV1[j][i] = 2*coeff(R[i], j);
383
}
384
385
// For convenience we also store the vertical reduction denominator
386
// D_V(t) = 2t - 1 as a pair of 1x1 matrices.
387
mat_ZZ_p DV0, DV1;
388
DV0.SetDims(1, 1);
389
DV1.SetDims(1, 1);
390
DV0[0][0] = -1;
391
DV1[0][0] = 2;
392
393
// =========================================================================
394
// Compute horizontal reduction matrices M_H^t(s) and denominators D_H^t(s),
395
// where t = (p(2j+1) - 1)/2, for j in the range 0 <= j < N.
396
// This is done to precision p^N.
397
modulus0.restore();
398
399
// We have D_H^t(s) = (2g+1)(2t-1) - 2s.
400
// The matrix M_H^t(s) has D_H^t(s) on the sub-diagonal entries, and the
401
// coefficients of the polynomial 2sQ(x) - (2t-1)xQ'(x) in the last column.
402
403
vector<mat_ZZ_p> MH0(N), MH1(N), DH0(N), DH1(N);
404
405
for (int j = 0; j < N; j++)
406
{
407
ZZ t = (p*(2*j+1) - 1)/2;
408
409
MH0[j].SetDims(2*g+1, 2*g+1);
410
MH1[j].SetDims(2*g+1, 2*g+1);
411
DH0[j].SetDims(1, 1);
412
DH1[j].SetDims(1, 1);
413
414
DH0[j][0][0] = to_ZZ_p((2*g+1)*(2*t-1));
415
DH1[j][0][0] = -2;
416
// subdiagonal entries:
417
for (int i = 0; i < 2*g; i++)
418
{
419
MH0[j][i+1][i] = DH0[j][0][0];
420
MH1[j][i+1][i] = DH1[j][0][0];
421
}
422
// last column:
423
for (int i = 0; i < 2*g+1; i++)
424
{
425
MH0[j][i][2*g] = -to_ZZ_p(i*(2*t-1))*coeff(Qred, i);
426
MH1[j][i][2*g] = 2*coeff(Qred, i);
427
}
428
}
429
430
// Compute inverse of the vandermonde matrix that will be used in the
431
// horizontal reduction phase
432
mat_ZZ_p vand_in, vand;
433
vand_in.SetDims(N, N);
434
for (int y = 1; y <= N; y++)
435
{
436
vand_in[y-1][0] = 1;
437
for (int x = 1; x < N; x++)
438
vand_in[y-1][x] = vand_in[y-1][x-1] * y;
439
}
440
padic_invert_matrix(vand, vand_in, p, N);
441
442
// =========================================================================
443
// Compute B_{j, r} coefficients.
444
// These are done to precision p^(N+1).
445
446
modulus1.restore();
447
448
// The relevant binomial coefficients.
449
vector<vector<ZZ_p> > binomial(2*N);
450
binomial[0].push_back(to_ZZ_p(1));
451
for (int j = 1; j < 2*N; j++)
452
{
453
binomial[j].push_back(to_ZZ_p(1));
454
for (int i = 1; i < j; i++)
455
binomial[j].push_back(binomial[j-1][i-1] + binomial[j-1][i]);
456
binomial[j].push_back(to_ZZ_p(1));
457
}
458
459
// For 0 <= j < N, compute Btemp[j] =
460
// (-1)^j \sum_{k=j}^{N-1} 4^{-k} {2k \choose k} {k \choose j}
461
ZZ_p fourth = to_ZZ_p(1) / 4;
462
ZZ_p fourth_pow = to_ZZ_p(1);
463
vector<ZZ_p> Btemp(N);
464
for (int k = 0; k < N; k++, fourth_pow = fourth_pow * fourth)
465
{
466
// even j
467
for (int j = 0; j <= k; j += 2)
468
Btemp[j] += binomial[2*k][k] * binomial[k][j] * fourth_pow;
469
// odd j
470
for (int j = 1; j <= k; j += 2)
471
Btemp[j] -= binomial[2*k][k] * binomial[k][j] * fourth_pow;
472
}
473
474
// Compute the coefficients B_{j, r} = p C_{j, r} (-1)^j
475
// \sum_{k=j}^{N-1} 4^{-k} {2k \choose k} {k \choose j},
476
// where C_{j, r} is the coefficient of x^r in Q(x)^j.
477
vector<vector<ZZ_p> > B(N);
478
ZZ_pX Qpow = to_ZZ_pX(p);
479
for (int j = 0; j < N; j++, Qpow *= Qred)
480
for (int r = 0; r <= (2*g+1)*j; r++)
481
B[j].push_back(Btemp[j] * coeff(Qpow, r));
482
483
// =========================================================================
484
// Horizontal reductions.
485
486
// reduced[j][i] will hold the reduction to x^0 of the i-th differential
487
// along row j (each entry is a vector of length 2*g)
488
vector<vector<vec_ZZ_p> > reduced(N, vector<vec_ZZ_p>(2*g));
489
490
for (int j = 0; j < N; j++)
491
{
492
// Compute the transition matrices, mod p^N, between the indices
493
// kp-2g-2 and kp, for each k. We compute at most N matrices (the others
494
// will be deduced more efficiently using the vandermonde matrix below)
495
modulus0.restore();
496
vector<ZZ> s;
497
int L = (2*g+1)*(j+1) - 1;
498
int L_effective = min(L, N);
499
for (int k = 0; k < L_effective; k++)
500
{
501
s.push_back(k*p);
502
s.push_back((k+1)*p - 2*g - 2);
503
}
504
vector<mat_ZZ_p> MH, DH;
505
MH.reserve(L);
506
DH.reserve(L);
507
interval_products_wrapper(MH, MH0[j], MH1[j], s, force_ntl);
508
interval_products_wrapper(DH, DH0[j], DH1[j], s, force_ntl);
509
510
// Use the vandermonde matrix to extend all the way up to L if necessary.
511
if (L > N)
512
{
513
// First compute X[r] = F^{(r)}(0) p^r / r! for r = 0, ..., N-1,
514
// where F is the matrix corresponding to M as described in the paper;
515
// and do the same for Y corresponding to the denominator D.
516
vector<mat_ZZ_p> X(N);
517
vector<ZZ_p> Y(N);
518
for (int r = 0; r < N; r++)
519
{
520
X[r].SetDims(2*g+1, 2*g+1);
521
for (int h = 0; h < N; h++)
522
{
523
ZZ_p& v = vand[r][h];
524
525
for (int y = 0; y < 2*g+1; y++)
526
for (int x = 0; x < 2*g+1; x++)
527
X[r][y][x] += v * MH[h][y][x];
528
529
Y[r] += v * DH[h][0][0];
530
}
531
}
532
533
// Now use those taylor coefficients to get the remaining MH's
534
// and DH's.
535
MH.resize(L);
536
DH.resize(L);
537
for (int k = N; k < L; k++)
538
{
539
MH[k].SetDims(2*g+1, 2*g+1);
540
DH[k].SetDims(1, 1);
541
542
// this is actually a power of k+1, since the indices into
543
// MH and DH are one-off from what's written in the paper
544
ZZ_p k_pow;
545
k_pow = 1;
546
547
for (int h = 0; h < N; h++, k_pow *= (k+1))
548
{
549
for (int y = 0; y < 2*g+1; y++)
550
for (int x = 0; x < 2*g+1; x++)
551
MH[k][y][x] += k_pow * X[h][y][x];
552
553
DH[k][0][0] += k_pow * Y[h];
554
}
555
}
556
}
557
558
// Divide out each MH[k] by DH[k].
559
for (int k = 0; k < MH.size(); k++)
560
{
561
ZZ_p Dinv = 1 / DH[k][0][0];
562
563
for (int x = 0; x < 2*g+1; x++)
564
for (int y = 0; y < 2*g+1; y++)
565
MH[k][y][x] *= Dinv;
566
}
567
568
// Reduce all differentials on this row into the x^0 position.
569
for (int i = 0; i < 2*g; i++)
570
{
571
modulus1.restore();
572
vec_ZZ_p sum;
573
sum.SetLength(2*g+1);
574
575
// loop over blocks in this row
576
for (int k = (2*g+1)*(j+1) - 1; k >= 1; k--)
577
{
578
// add in new term if necessary
579
if (k >= i+1 && k <= i+1 + (2*g+1)*j)
580
sum[0] += B[j][k - (i+1)];
581
582
// We do the following reductions "by hand" for a little more
583
// efficiency, since the matrices are all sparse.
584
585
// tt is 2t - 1 in the notation of the paper
586
ZZ tt = (2*j+1)*p - 2;
587
588
// Reduce from kp - 1 down to kp - 2g - 1.
589
for (int u = 1; u <= 2*g; u++)
590
{
591
// c is the last component
592
ZZ_p c = sum[2*g];
593
594
// shuffle over all components by one step
595
for (int m = 2*g; m >= 1; m--)
596
sum[m] = sum[m-1];
597
sum[0] = 0;
598
599
// add in contribution from c (these follow from the explicit
600
// formulae for the horizontal reductions)
601
ZZ s = k*p - u;
602
c /= to_ZZ_p((2*g+1)*tt - 2*s);
603
for (int m = 0; m <= 2*g; m++)
604
sum[m] += c * to_ZZ_p(2*s - tt*m) * coeff(Qred, m);
605
}
606
607
// Reduce from kp - 2g - 1 to kp - 2g - 2.
608
// This is a little different to the previous block owing to the
609
// denominator being divisible by p.
610
{
611
// c is the last component
612
ZZ_p c = sum[2*g];
613
614
assert(rep(c) % p == 0);
615
c = to_ZZ_p(rep(c) / p);
616
617
// shuffle over all components by one step
618
for (int m = 2*g; m >= 1; m--)
619
sum[m] = sum[m-1];
620
sum[0] = 0;
621
622
// add in contribution from c
623
ZZ s = k*p - (2*g + 1);
624
ZZ denom = (2*g+1)*tt - 2*s;
625
assert(denom % p == 0);
626
c /= to_ZZ_p(denom / p);
627
for (int m = 0; m <= 2*g; m++)
628
sum[m] += c * to_ZZ_p(2*s - tt*m) * coeff(Qred, m);
629
}
630
631
// Use big fat matrices to reduce from kp - 2g - 2 to (k-1)p.
632
{
633
// drop precision to p^N and apply the matrix
634
modulus0.restore();
635
vec_ZZ_p temp = MH[k-1] * to_vec_ZZ_p(to_vec_ZZ(sum));
636
637
// lift precision back to p^(N+1)
638
modulus1.restore();
639
sum = to_vec_ZZ_p(to_vec_ZZ(temp));
640
}
641
642
// Reduce from (k-1)p down to (k-1)p - 1.
643
{
644
// c is the last component
645
ZZ_p c = sum[2*g];
646
647
// shuffle over all components by one step
648
for (int m = 2*g; m >= 1; m--)
649
sum[m] = sum[m-1];
650
sum[0] = 0;
651
652
// add in contribution from c
653
ZZ s = (k-1)*p;
654
c /= to_ZZ_p((2*g+1)*tt - 2*s);
655
for (int m = 0; m <= 2*g; m++)
656
sum[m] += c * to_ZZ_p(2*s - tt*m) * coeff(Qred, m);
657
}
658
}
659
660
// Store the reduced vector. Note that currently it has length 2g+1,
661
// but the lowest term is zero, and the vertical reductions will
662
// operate on length 2g vectors, so we kill the lowest term.
663
modulus1.restore();
664
reduced[j][i].SetLength(2*g);
665
for (int f = 0; f < 2*g; f++)
666
reduced[j][i][f] = sum[f+1];
667
}
668
}
669
670
if (N == 1)
671
{
672
// If N == 1, then the vertical matrices are stored at precision 1
673
// (instead of at N+1), so we reduce "reduced" to precision 1 here.
674
modulus0.restore();
675
for (int j = 0; j < N; j++)
676
for (int i = 0; i < 2*g; i++)
677
reduced[j][i] = to_vec_ZZ_p(to_vec_ZZ(reduced[j][i]));
678
}
679
680
// =========================================================================
681
// Vertical reductions.
682
683
// Compute the vertical reduction maps between indices
684
// 0, (p-1)/2, (3p-1)/2, (5p-1)/2, ..., ((2N-1)p-1)/2.
685
vector<ZZ> s;
686
s.push_back(to_ZZ(0));
687
s.push_back((p-1)/2);
688
for (int u = 1; u < N; u++)
689
{
690
s.push_back(s.back());
691
s.push_back(s.back() + p);
692
}
693
modulusV.restore();
694
vector<mat_ZZ_p> MV, DV;
695
interval_products_wrapper(MV, MV0, MV1, s, force_ntl);
696
interval_products_wrapper(DV, DV0, DV1, s, force_ntl);
697
698
// Divide out each MV[j] by DV[j]. Note that for 1 <= j < N, both of them
699
// should be divisible by p too, so we take that into account here.
700
for (int j = 0; j < N; j++)
701
{
702
ZZ u = rep(DV[j][0][0]);
703
if (j != 0)
704
{
705
assert(u % p == 0);
706
u /= p;
707
}
708
InvMod(u, u, ZZ_p::modulus());
709
710
for (int x = 0; x < 2*g; x++)
711
for (int y = 0; y < 2*g; y++)
712
{
713
ZZ c = rep(MV[j][y][x]);
714
if (j != 0)
715
{
716
assert(c % p == 0);
717
c /= p;
718
}
719
MV[j][y][x] = to_ZZ_p(c * u);
720
}
721
}
722
723
output.SetDims(2*g, 2*g);
724
ZZ p_to_N = power(p, N);
725
726
// For each i, use the above reductions matrices to vertically reduce the
727
// terms corresponding to x^i dx/y.
728
for (int i = 0; i < 2*g; i++)
729
{
730
vec_ZZ_p sum;
731
sum.SetLength(2*g);
732
733
for (int j = N-1; j >= 0; j--)
734
{
735
// pick up a new term,
736
sum += reduced[j][i];
737
// and reduce:
738
sum = MV[j] * sum;
739
}
740
741
// store the result
742
for (int f = 0; f < 2*g; f++)
743
output[f][i] = rep(sum[f]) % p_to_N;
744
}
745
746
// Restore the caller's modulus
747
modulus_caller.restore();
748
749
return 1;
750
}
751
752
753
}; // namespace hypellfrob
754
755
// ----------------------- end of file
756
757