Line data Source code
1 : /* Copyright (C) 2014 The PARI group.
2 :
3 : This file is part of the PARI/GP package.
4 :
5 : PARI/GP is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 2 of the License, or (at your option) any later
8 : version. It is distributed in the hope that it will be useful, but WITHOUT
9 : ANY WARRANTY WHATSOEVER.
10 :
11 : Check the License for details. You should have received a copy of it, along
12 : with the package; see the file 'COPYING'. If not, write to the Free Software
13 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
14 :
15 : #include "pari.h"
16 : #include "paripriv.h"
17 :
18 : /* FIXME: Implement {ascend,descend}_volcano() in terms of the "new"
19 : * volcano traversal functions at the bottom of the file. */
20 :
21 : /* Is j = 0 or 1728 (mod p)? */
22 : INLINE int
23 325436 : is_j_exceptional(ulong j, ulong p) { return j == 0 || j == 1728 % p; }
24 :
25 : INLINE long
26 73183 : node_degree(GEN phi, long L, ulong j, ulong p, ulong pi)
27 : {
28 73183 : pari_sp av = avma;
29 73183 : long n = Flx_nbroots(Flm_Fl_polmodular_evalx(phi, L, j, p, pi), p);
30 73183 : return gc_long(av, n);
31 : }
32 :
33 : /* Given an array path = [j0, j1] of length 2, return the polynomial
34 : *
35 : * \Phi_L(X, j1) / (X - j0)
36 : *
37 : * where \Phi_L(X, Y) is the modular polynomial of level L. An error
38 : * is raised if X - j0 does not divide \Phi_L(X, j1) */
39 : INLINE GEN
40 128539 : nhbr_polynomial(ulong path[], GEN phi, ulong p, ulong pi, long L)
41 : {
42 128539 : pari_sp ltop = avma;
43 128539 : GEN modpol = Flm_Fl_polmodular_evalx(phi, L, path[0], p, pi);
44 : ulong rem;
45 128539 : GEN nhbr_pol = Flx_div_by_X_x(modpol, path[-1], p, &rem);
46 : /* If disc End(path[0]) <= L^2, it's possible for path[0] to appear among the
47 : * roots of nhbr_pol. This should have been obviated by earlier choices */
48 128539 : if (rem) pari_err_BUG("nhbr_polynomial: invalid preceding j");
49 128539 : return gerepileupto(ltop, nhbr_pol);
50 : }
51 :
52 : /* Path is an array with space for at least max_len+1 * elements, whose first
53 : * and second elements are the beginning of the path. I.e., the path starts
54 : * (path[0], path[1])
55 : * If the result is less than max_len, then the last element of path is on the
56 : * floor. If the result equals max_len, then it is unknown whether the last
57 : * element of path is on the floor or not */
58 : static long
59 251789 : extend_path(ulong path[], GEN phi, ulong p, ulong pi, long L, long max_len)
60 : {
61 251789 : pari_sp av = avma;
62 251789 : long d = 1;
63 324860 : for ( ; d < max_len; d++)
64 : {
65 94468 : GEN nhbr_pol = nhbr_polynomial(path + d, phi, p, pi, L);
66 94468 : ulong nhbr = Flx_oneroot(nhbr_pol, p);
67 94468 : set_avma(av);
68 94468 : if (nhbr == p) break; /* no root: we are on the floor. */
69 73071 : path[d+1] = nhbr;
70 : }
71 251789 : return d;
72 : }
73 :
74 : /* This is Sutherland 2009 Algorithm Ascend (p12) */
75 : ulong
76 114601 : ascend_volcano(GEN phi, ulong j, ulong p, ulong pi, long level, long L,
77 : long depth, long steps)
78 : {
79 114601 : pari_sp ltop = avma, av;
80 : /* path will never hold more than max_len < depth elements */
81 114601 : GEN path_g = cgetg(depth + 2, t_VECSMALL);
82 114601 : ulong *path = zv_to_ulongptr(path_g);
83 114601 : long max_len = depth - level;
84 114601 : int first_iter = 1;
85 :
86 114601 : if (steps <= 0 || max_len < 0) pari_err_BUG("ascend_volcano: bad params");
87 114601 : av = avma;
88 263273 : while (steps--)
89 : {
90 114601 : GEN nhbr_pol = first_iter? Flm_Fl_polmodular_evalx(phi, L, j, p, pi)
91 148672 : : nhbr_polynomial(path+1, phi, p, pi, L);
92 148672 : GEN nhbrs = Flx_roots(nhbr_pol, p);
93 148672 : long nhbrs_len = lg(nhbrs)-1, i;
94 148672 : pari_sp btop = avma;
95 148672 : path[0] = j;
96 148672 : first_iter = 0;
97 :
98 148672 : j = nhbrs[nhbrs_len];
99 187727 : for (i = 1; i < nhbrs_len; i++)
100 : {
101 70639 : ulong next_j = nhbrs[i], last_j;
102 : long len;
103 70639 : if (is_j_exceptional(next_j, p))
104 : {
105 : /* Fouquet & Morain, Section 4.3, if j = 0 or 1728, then it is on the
106 : * surface. So we just return it. */
107 21 : if (steps)
108 0 : pari_err_BUG("ascend_volcano: Got to the top with more steps to go!");
109 21 : j = next_j; break;
110 : }
111 70618 : path[1] = next_j;
112 70618 : len = extend_path(path, phi, p, pi, L, max_len);
113 70618 : last_j = path[len];
114 70618 : if (len == max_len
115 : /* Ended up on the surface */
116 70618 : && (is_j_exceptional(last_j, p)
117 70618 : || node_degree(phi, L, last_j, p, pi) > 1)) { j = next_j; break; }
118 39055 : set_avma(btop);
119 : }
120 148672 : path[1] = j; /* For nhbr_polynomial() at the top. */
121 :
122 148672 : max_len++; set_avma(av);
123 : }
124 114601 : return gc_long(ltop, j);
125 : }
126 :
127 : static void
128 184179 : random_distinct_neighbours_of(ulong *nhbr1, ulong *nhbr2,
129 : GEN phi, ulong j, ulong p, ulong pi, long L, long must_have_two_neighbours)
130 : {
131 184179 : pari_sp av = avma;
132 184179 : GEN modpol = Flm_Fl_polmodular_evalx(phi, L, j, p, pi);
133 : ulong rem;
134 184179 : *nhbr1 = Flx_oneroot(modpol, p);
135 184179 : if (*nhbr1 == p) pari_err_BUG("random_distinct_neighbours_of [no neighbour]");
136 184179 : modpol = Flx_div_by_X_x(modpol, *nhbr1, p, &rem);
137 184179 : *nhbr2 = Flx_oneroot(modpol, p);
138 184179 : if (must_have_two_neighbours && *nhbr2 == p)
139 0 : pari_err_BUG("random_distinct_neighbours_of [single neighbour]");
140 184179 : set_avma(av);
141 184179 : }
142 :
143 : /*
144 : * This is Sutherland 2009 Algorithm Descend (p12).
145 : */
146 : ulong
147 2416 : descend_volcano(GEN phi, ulong j, ulong p, ulong pi,
148 : long level, long L, long depth, long steps)
149 : {
150 2416 : pari_sp ltop = avma;
151 : GEN path_g;
152 : ulong *path;
153 : long max_len;
154 :
155 2416 : if (steps <= 0 || level + steps > depth) pari_err_BUG("descend_volcano");
156 2416 : max_len = depth - level;
157 2416 : path_g = cgetg(max_len + 1 + 1, t_VECSMALL);
158 2417 : path = zv_to_ulongptr(path_g);
159 2417 : path[0] = j;
160 : /* level = 0 means we're on the volcano surface... */
161 2417 : if (!level)
162 : {
163 : /* Look for any path to the floor. One of j's first three neighbours leads
164 : * to the floor, since at most two neighbours are on the surface. */
165 2179 : GEN nhbrs = Flx_roots(Flm_Fl_polmodular_evalx(phi, L, j, p, pi), p);
166 : long i;
167 2349 : for (i = 1; i <= 3; i++)
168 : {
169 : long len;
170 2349 : path[1] = nhbrs[i];
171 2349 : len = extend_path(path, phi, p, pi, L, max_len);
172 : /* If nhbrs[i] took us to the floor: */
173 2349 : if (len < max_len || node_degree(phi, L, path[len], p, pi) == 1) break;
174 : }
175 2179 : if (i > 3) pari_err_BUG("descend_volcano [2]");
176 : }
177 : else
178 : {
179 : ulong nhbr1, nhbr2;
180 : long len;
181 238 : random_distinct_neighbours_of(&nhbr1, &nhbr2, phi, j, p, pi, L, 1);
182 238 : path[1] = nhbr1;
183 238 : len = extend_path(path, phi, p, pi, L, max_len);
184 : /* If last j isn't on the floor */
185 238 : if (len == max_len /* Ended up on the surface. */
186 238 : && (is_j_exceptional(path[len], p)
187 216 : || node_degree(phi, L, path[len], p, pi) != 1)) {
188 : /* The other neighbour leads to the floor */
189 96 : path[1] = nhbr2;
190 96 : (void) extend_path(path, phi, p, pi, L, steps);
191 : }
192 : }
193 2417 : return gc_ulong(ltop, path[steps]);
194 : }
195 :
196 : long
197 183941 : j_level_in_volcano(
198 : GEN phi, ulong j, ulong p, ulong pi, long L, long depth)
199 : {
200 183941 : pari_sp av = avma;
201 : GEN chunk;
202 : ulong *path1, *path2;
203 : long lvl;
204 :
205 : /* Fouquet & Morain, Section 4.3, if j = 0 or 1728 then it is on the
206 : * surface. Also, if the volcano depth is zero then j has level 0 */
207 183941 : if (depth == 0 || is_j_exceptional(j, p)) return 0;
208 :
209 183941 : chunk = new_chunk(2 * (depth + 1));
210 183941 : path1 = (ulong *) &chunk[0];
211 183941 : path2 = (ulong *) &chunk[depth + 1];
212 183941 : path1[0] = path2[0] = j;
213 183941 : random_distinct_neighbours_of(&path1[1], &path2[1], phi, j, p, pi, L, 0);
214 183941 : if (path2[1] == p)
215 94697 : lvl = depth; /* Only one neighbour => j is on the floor => level = depth */
216 : else
217 : {
218 89244 : long path1_len = extend_path(path1, phi, p, pi, L, depth);
219 89244 : long path2_len = extend_path(path2, phi, p, pi, L, path1_len);
220 89244 : lvl = depth - path2_len;
221 : }
222 183941 : return gc_long(av, lvl);
223 : }
224 :
225 : #define vecsmall_len(v) (lg(v) - 1)
226 :
227 : INLINE GEN
228 30857059 : Flx_remove_root(GEN f, ulong a, ulong p)
229 : {
230 : ulong r;
231 30857059 : GEN g = Flx_div_by_X_x(f, a, p, &r);
232 30762043 : if (r) pari_err_BUG("Flx_remove_root");
233 30771004 : return g;
234 : }
235 :
236 : INLINE GEN
237 23399589 : get_nbrs(GEN phi, long L, ulong J, const ulong *xJ, ulong p, ulong pi)
238 : {
239 23399589 : pari_sp av = avma;
240 23399589 : GEN f = Flm_Fl_polmodular_evalx(phi, L, J, p, pi);
241 23409391 : if (xJ) f = Flx_remove_root(f, *xJ, p);
242 23351912 : return gerepileupto(av, Flx_roots(f, p));
243 : }
244 :
245 : /* Return a path of length n along the surface of an L-volcano of height h
246 : * starting from surface node j0. Assumes (D|L) = 1 where D = disc End(j0).
247 : *
248 : * Actually, if j0's endomorphism ring is a suborder, we return the
249 : * corresponding shorter path. W must hold space for n + h nodes.
250 : *
251 : * TODO: have two versions of this function: one that assumes J has the correct
252 : * endomorphism ring (hence avoiding several branches in the inner loop) and a
253 : * second that does not and accordingly checks for repetitions */
254 : static long
255 189477 : surface_path(
256 : ulong W[],
257 : long n, GEN phi, long L, long h, ulong J, const ulong *nJ, ulong p, ulong pi)
258 : {
259 189477 : pari_sp av = avma, bv;
260 : GEN T, v;
261 : long j, k, w, x;
262 : ulong W0;
263 :
264 189477 : W[0] = W0 = J;
265 189477 : if (n == 1) return 1;
266 :
267 189477 : T = cgetg(h+2, t_VEC); bv = avma;
268 189477 : v = get_nbrs(phi, L, J, nJ, p, pi);
269 : /* Insert known neighbour first */
270 189472 : if (nJ) v = gerepileupto(bv, vecsmall_prepend(v, *nJ));
271 189472 : gel(T,1) = v;
272 189472 : k = vecsmall_len(v);
273 :
274 189472 : switch (k) {
275 0 : case 0: pari_err_BUG("surface_path"); /* We must always have neighbours */
276 5928 : case 1:
277 : /* If volcano is not flat, then we must have more than one neighbour */
278 5928 : if (h) pari_err_BUG("surface_path");
279 5928 : W[1] = uel(v, 1);
280 5928 : set_avma(av);
281 : /* Check for bad endo ring */
282 5928 : if (W[1] == W[0]) return 1;
283 5848 : return 2;
284 21144 : case 2:
285 : /* If L=2 the only way we can have 2 neighbours is if we have a double root
286 : * which can only happen for |D| <= 16 (Thm 2.2 of Fouquet-Morain)
287 : * and if it does we must have a 2-cycle. Happens for D=-15. */
288 21144 : if (L == 2)
289 : { /* The double root is the neighbour on the surface, with exactly one
290 : * neighbour other than J; the other neighbour of J has either 0 or 2
291 : * neighbours that are not J */
292 42 : GEN u = get_nbrs(phi, L, uel(v, 1), &J, p, pi);
293 42 : long n = vecsmall_len(u) - !!vecsmall_isin(u, J);
294 42 : W[1] = n == 1 ? uel(v,1) : uel(v,2);
295 42 : return gc_long(av, 2);
296 : }
297 : /* Volcano is not flat but found only 2 neighbours for the surface node J */
298 21102 : if (h) pari_err_BUG("surface_path");
299 :
300 21102 : W[1] = uel(v,1); /* TODO: Can we use the other root uel(v,2) somehow? */
301 4391888 : for (w = 2; w < n; w++)
302 : {
303 4370931 : v = get_nbrs(phi, L, W[w-1], &W[w-2], p, pi);
304 : /* A flat volcano must have exactly one non-previous neighbour */
305 4371001 : if (vecsmall_len(v) != 1) pari_err_BUG("surface_path");
306 4371001 : W[w] = uel(v, 1);
307 : /* Detect cycle in case J doesn't have the right endo ring. */
308 4371001 : set_avma(av); if (W[w] == W0) return w;
309 : }
310 20957 : return gc_long(av, n);
311 : }
312 162400 : if (!h) pari_err_BUG("surface_path"); /* Can't have a flat volcano if k > 2 */
313 :
314 : /* At this point, each surface node has L+1 distinct neighbours, 2 of which
315 : * are on the surface */
316 162399 : w = 1;
317 162399 : for (x = 0;; x++)
318 : {
319 : /* Get next neighbour of last known surface node to attempt to
320 : * extend the path. */
321 5983910 : W[w] = umael(T, ((w-1) % h) + 1, x + 1);
322 :
323 : /* Detect cycle in case the given J didn't have the right endo ring */
324 6146309 : if (W[w] == W0) return gc_long(av,w);
325 :
326 : /* If we have to test the last neighbour, we know it's on the
327 : * surface, and if we're done there's no need to extend. */
328 6146198 : if (x == k-1 && w == n-1) return gc_long(av,n);
329 :
330 : /* Walk forward until we hit the floor or finish. */
331 : /* NB: We don't keep the stack clean here; usage is in the order of Lh,
332 : * i.e. L roots for each level of the volcano of height h. */
333 6039539 : for (j = w;;)
334 12658677 : {
335 : long m;
336 : /* We must get 0 or L neighbours here. */
337 18698216 : v = get_nbrs(phi, L, W[j], &W[j-1], p, pi);
338 18658836 : m = vecsmall_len(v);
339 18658836 : if (!m) {
340 : /* We hit the floor: save the neighbours of W[w-1] and dump the rest */
341 5981660 : GEN nbrs = gel(T, ((w-1) % h) + 1);
342 5981660 : gel(T, ((w-1) % h) + 1) = gerepileupto(bv, nbrs);
343 5983910 : break;
344 : }
345 12677176 : if (m != L) pari_err_BUG("surface_path");
346 :
347 12714303 : gel(T, (j % h) + 1) = v;
348 :
349 12714303 : W[++j] = uel(v, 1);
350 : /* If we have our path by h nodes, we know W[w] is on the surface */
351 12714303 : if (j == w + h) {
352 11732942 : ++w;
353 : /* Detect cycle in case the given J didn't have the right endo ring */
354 11732942 : if (W[w] == W0) return gc_long(av,w);
355 11707324 : x = 0; k = L;
356 : }
357 12688685 : if (w == n) return gc_long(av,w);
358 : }
359 : }
360 : }
361 :
362 : long
363 119023 : next_surface_nbr(
364 : ulong *nJ,
365 : GEN phi, long L, long h, ulong J, const ulong *pJ, ulong p, ulong pi)
366 : {
367 119023 : pari_sp av = avma, bv;
368 : GEN S;
369 : ulong *P;
370 : long i, k;
371 :
372 119023 : S = get_nbrs(phi, L, J, pJ, p, pi);
373 119020 : k = vecsmall_len(S);
374 : /* If there is a double root and pJ is set, then k will be zero. */
375 119020 : if (!k) return gc_long(av,0);
376 119020 : if (k == 1 || ( ! pJ && k == 2)) { *nJ = uel(S, 1); return gc_long(av,1); }
377 18590 : if (!h) pari_err_BUG("next_surface_nbr");
378 :
379 18590 : P = (ulong *) new_chunk(h + 1); bv = avma;
380 18590 : P[0] = J;
381 36291 : for (i = 0; i < k; i++)
382 : {
383 : long j;
384 36291 : P[1] = uel(S, i + 1);
385 59713 : for (j = 1; j <= h; j++)
386 : {
387 41123 : GEN T = get_nbrs(phi, L, P[j], &P[j - 1], p, pi);
388 41123 : if (!vecsmall_len(T)) break;
389 23422 : P[j + 1] = uel(T, 1);
390 : }
391 36291 : if (j < h) pari_err_BUG("next_surface_nbr");
392 36291 : set_avma(bv); if (j > h) break;
393 : }
394 : /* TODO: We could save one get_nbrs call by iterating from i up to k-1 and
395 : * assume that the last (kth) nbr is the one we want. For now we're careful
396 : * and check that this last nbr really is on the surface */
397 18590 : if (i == k) pari_err_BUG("next_surf_nbr");
398 18590 : *nJ = uel(S, i+1); return gc_long(av,1);
399 : }
400 :
401 : /* Return the number of distinct neighbours (1 or 2) */
402 : INLINE long
403 179456 : common_nbr(ulong *nbr,
404 : ulong J1, GEN Phi1, long L1,
405 : ulong J2, GEN Phi2, long L2, ulong p, ulong pi)
406 : {
407 179456 : pari_sp av = avma;
408 : GEN d, f, g, r;
409 : long rlen;
410 :
411 179456 : g = Flm_Fl_polmodular_evalx(Phi1, L1, J1, p, pi);
412 179456 : f = Flm_Fl_polmodular_evalx(Phi2, L2, J2, p, pi);
413 179458 : d = Flx_gcd(f, g, p);
414 179452 : if (degpol(d) == 1) { *nbr = Flx_deg1_root(d, p); return gc_long(av,1); }
415 1093 : if (degpol(d) != 2) pari_err_BUG("common_neighbour");
416 1093 : r = Flx_roots(d, p);
417 1093 : rlen = vecsmall_len(r);
418 1093 : if (!rlen) pari_err_BUG("common_neighbour");
419 : /* rlen is 1 or 2 depending on whether the root is unique or not. */
420 1093 : nbr[0] = uel(r, 1);
421 1093 : nbr[1] = uel(r, rlen); return gc_long(av,rlen);
422 : }
423 :
424 : /* Return gcd(Phi1(X,J1)/(X - J0), Phi2(X,J2)). Not stack clean. */
425 : INLINE GEN
426 42264 : common_nbr_pred_poly(
427 : ulong J1, GEN Phi1, long L1,
428 : ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
429 : {
430 : GEN f, g;
431 :
432 42264 : g = Flm_Fl_polmodular_evalx(Phi1, L1, J1, p, pi);
433 42264 : g = Flx_remove_root(g, J0, p);
434 42264 : f = Flm_Fl_polmodular_evalx(Phi2, L2, J2, p, pi);
435 42264 : return Flx_gcd(f, g, p);
436 : }
437 :
438 : /* Find common neighbour of J1 and J2, where J0 is an L1 predecessor of J1.
439 : * Return 1 if successful, 0 if not. */
440 : INLINE int
441 41181 : common_nbr_pred(ulong *nbr,
442 : ulong J1, GEN Phi1, long L1,
443 : ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
444 : {
445 41181 : pari_sp av = avma;
446 41181 : GEN d = common_nbr_pred_poly(J1, Phi1, L1, J2, Phi2, L2, J0, p, pi);
447 41181 : int res = (degpol(d) == 1);
448 41181 : if (res) *nbr = Flx_deg1_root(d, p);
449 41181 : return gc_bool(av,res);
450 : }
451 :
452 : INLINE long
453 1083 : common_nbr_verify(ulong *nbr,
454 : ulong J1, GEN Phi1, long L1,
455 : ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
456 : {
457 1083 : pari_sp av = avma;
458 1083 : GEN d = common_nbr_pred_poly(J1, Phi1, L1, J2, Phi2, L2, J0, p, pi);
459 :
460 1083 : if (!degpol(d)) return gc_long(av,0);
461 420 : if (degpol(d) > 1) pari_err_BUG("common_neighbour_verify");
462 420 : *nbr = Flx_deg1_root(d, p);
463 420 : return gc_long(av,1);
464 : }
465 :
466 : INLINE ulong
467 425 : Flm_Fl_polmodular_evalxy(GEN Phi, long L, ulong x, ulong y, ulong p, ulong pi)
468 : {
469 425 : pari_sp av = avma;
470 425 : GEN f = Flm_Fl_polmodular_evalx(Phi, L, x, p, pi);
471 425 : return gc_ulong(av, Flx_eval_pre(f, y, p, pi));
472 : }
473 :
474 : /* Find a common L1-neighbor of J1 and L2-neighbor of J2, given J0 an
475 : * L2-neighbor of J1 and an L1-neighbor of J2. Return 1 if successful, 0
476 : * otherwise. Will only fail if initial J-invariant had the wrong endo ring */
477 : INLINE int
478 22316 : common_nbr_corner(ulong *nbr,
479 : ulong J1, GEN Phi1, long L1, long h1,
480 : ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
481 : {
482 : ulong nbrs[2];
483 22316 : if (common_nbr(nbrs, J1,Phi1,L1, J2,Phi2,L2, p, pi) == 2)
484 : {
485 : ulong nJ1, nJ2;
486 528 : if (!next_surface_nbr(&nJ2, Phi1, L1, h1, J2, &J0, p, pi) ||
487 316 : !next_surface_nbr(&nJ1, Phi1, L1, h1, nbrs[0], &J1, p, pi)) return 0;
488 :
489 264 : if (Flm_Fl_polmodular_evalxy(Phi2, L2, nJ1, nJ2, p, pi))
490 103 : nbrs[0] = nbrs[1];
491 322 : else if (!next_surface_nbr(&nJ1, Phi1, L1, h1, nbrs[1], &J1, p, pi) ||
492 213 : !Flm_Fl_polmodular_evalxy(Phi2, L2, nJ1, nJ2, p, pi)) return 0;
493 : }
494 22264 : *nbr = nbrs[0]; return 1;
495 : }
496 :
497 : /* Enumerate a surface L1-cycle using gcds with Phi_L2, where c_L2=c_L1^e and
498 : * |c_L1|=n, where c_a is the class of the pos def reduced primeform <a,b,c>.
499 : * Assumes n > e > 1 and roots[0],...,roots[e-1] are already present in W */
500 : static long
501 60146 : surface_gcd_cycle(
502 : ulong W[], ulong V[], long n,
503 : GEN Phi1, long L1, GEN Phi2, long L2, long e, ulong p, ulong pi)
504 : {
505 60146 : pari_sp av = avma;
506 : long i1, i2, j1, j2;
507 :
508 60146 : i1 = j2 = 0;
509 60146 : i2 = j1 = e - 1;
510 : /* If W != V we assume V actually points to an L2-isogenous parallel L1-path.
511 : * e should be 2 in this case */
512 60146 : if (W != V) { i1 = j1+1; i2 = n-1; }
513 : do {
514 : ulong t0, t1, t2, h10, h11, h20, h21;
515 : long k;
516 : GEN f, g, h1, h2;
517 :
518 3874081 : f = Flm_Fl_polmodular_evalx(Phi2, L2, V[i1], p, pi);
519 3862409 : g = Flm_Fl_polmodular_evalx(Phi1, L1, W[j1], p, pi);
520 3865126 : g = Flx_remove_root(g, W[j1 - 1], p);
521 3857675 : h1 = Flx_gcd(f, g, p);
522 3846981 : if (degpol(h1) != 1) break; /* Error */
523 3846275 : h11 = Flx_coeff(h1, 1);
524 3847364 : h10 = Flx_coeff(h1, 0); set_avma(av);
525 :
526 3845781 : f = Flm_Fl_polmodular_evalx(Phi2, L2, V[i2], p, pi);
527 3862882 : g = Flm_Fl_polmodular_evalx(Phi1, L1, W[j2], p, pi);
528 3865573 : k = j2 + 1;
529 3865573 : if (k == n) k = 0;
530 3865573 : g = Flx_remove_root(g, W[k], p);
531 3859254 : h2 = Flx_gcd(f, g, p);
532 3848757 : if (degpol(h2) != 1) break; /* Error */
533 3848121 : h21 = Flx_coeff(h2, 1);
534 3849246 : h20 = Flx_coeff(h2, 0); set_avma(av);
535 :
536 3847668 : i1++; i2--; if (i2 < 0) i2 = n-1;
537 3847668 : j1++; j2--; if (j2 < 0) j2 = n-1;
538 :
539 3847668 : t0 = Fl_mul_pre(h11, h21, p, pi);
540 3871534 : t1 = Fl_inv(t0, p);
541 3871171 : t0 = Fl_mul_pre(t1, h21, p, pi);
542 3874963 : t2 = Fl_mul_pre(t0, h10, p, pi);
543 3874998 : W[j1] = Fl_neg(t2, p);
544 3873550 : t0 = Fl_mul_pre(t1, h11, p, pi);
545 3875867 : t2 = Fl_mul_pre(t0, h20, p, pi);
546 3875632 : W[j2] = Fl_neg(t2, p);
547 3873951 : } while (j2 > j1 + 1);
548 : /* Usually the loop exits when j2 = j1 + 1, in which case we return n.
549 : * If we break early because of an error, then (j2 - (j1+1)) > 0 is the
550 : * number of elements we haven't calculated yet, and we return n minus that
551 : * quantity */
552 60016 : return gc_long(av, n - j2 + j1 + 1);
553 : }
554 :
555 : static long
556 1176 : surface_gcd_path(
557 : ulong W[], ulong V[], long n,
558 : GEN Phi1, long L1, GEN Phi2, long L2, long e, ulong p, ulong pi)
559 : {
560 1176 : pari_sp av = avma;
561 : long i, j;
562 :
563 1176 : i = 0; j = e;
564 : /* If W != V then assume V actually points to a L2-isogenous
565 : * parallel L1-path. e should be 2 in this case */
566 1176 : if (W != V) i = j;
567 4872 : while (j < n)
568 : {
569 : GEN f, g, d;
570 :
571 3696 : f = Flm_Fl_polmodular_evalx(Phi2, L2, V[i], p, pi);
572 3696 : g = Flm_Fl_polmodular_evalx(Phi1, L1, W[j - 1], p, pi);
573 3696 : g = Flx_remove_root(g, W[j - 2], p);
574 3696 : d = Flx_gcd(f, g, p);
575 3696 : if (degpol(d) != 1) break; /* Error */
576 3696 : W[j] = Flx_deg1_root(d, p);
577 3696 : i++; j++; set_avma(av);
578 : }
579 1176 : return gc_long(av, j);
580 : }
581 :
582 : /* Given a path V of length n on an L1-volcano, and W[0] L2-isogenous to V[0],
583 : * extends the path W to length n on an L1-volcano, with W[i] L2-isogenous
584 : * to V[i]. Uses gcds unless L2 is too large to make it helpful. Always uses
585 : * GCD to get W[1] to ensure consistent orientation.
586 : *
587 : * Returns the new length of W. This will almost always be n, but could be
588 : * lower if V was started with a J-invariant with bad endomorphism ring */
589 : INLINE long
590 157140 : surface_parallel_path(
591 : ulong W[], ulong V[], long n,
592 : GEN Phi1, long L1, GEN Phi2, long L2, ulong p, ulong pi, long cycle)
593 : {
594 : ulong W2, nbrs[2];
595 157140 : if (common_nbr(nbrs, W[0], Phi1, L1, V[1], Phi2, L2, p, pi) == 2)
596 : {
597 731 : if (n <= 2) return 1; /* Error: Two choices with n = 2; ambiguous */
598 731 : if (!common_nbr_verify(&W2,nbrs[0], Phi1,L1,V[2], Phi2,L2,W[0], p,pi))
599 379 : nbrs[0] = nbrs[1]; /* nbrs[1] must be the correct choice */
600 352 : else if (common_nbr_verify(&W2,nbrs[1], Phi1,L1,V[2], Phi2,L2,W[0], p,pi))
601 68 : return 1; /* Error: Both paths extend successfully */
602 : }
603 157074 : W[1] = nbrs[0];
604 157074 : if (n <= 2) return n;
605 60146 : return cycle? surface_gcd_cycle(W, V, n, Phi1, L1, Phi2, L2, 2, p, pi)
606 121472 : : surface_gcd_path (W, V, n, Phi1, L1, Phi2, L2, 2, p, pi);
607 : }
608 :
609 : GEN
610 190367 : enum_roots(ulong J0, norm_eqn_t ne, GEN fdb, classgp_pcp_t G)
611 : { /* MAX_HEIGHT >= max_{p,n} val_p(n) where p and n are ulongs */
612 : enum { MAX_HEIGHT = BITS_IN_LONG };
613 190367 : pari_sp av, ltop = avma;
614 190367 : long s = !!G->L0;
615 190367 : long *n = G->n + s, *L = G->L + s, *o = G->o + s, k = G->k - s;
616 190367 : long i, t, vlen, *e, *h, *off, *poff, *M, N = G->enum_cnt;
617 190367 : ulong p = ne->p, pi = ne->pi, *roots;
618 : GEN Phi, vshape, vp, ve, roots_;
619 :
620 190367 : if (!k) return mkvecsmall(J0);
621 :
622 189474 : roots_ = cgetg(N + MAX_HEIGHT, t_VECSMALL);
623 189474 : roots = zv_to_ulongptr(roots_);
624 189474 : av = avma;
625 :
626 : /* TODO: Shouldn't be factoring this every time. Store in *ne? */
627 189474 : vshape = factoru(ne->v);
628 189478 : vp = gel(vshape, 1);
629 189478 : ve = gel(vshape, 2);
630 :
631 189478 : vlen = vecsmall_len(vp);
632 189478 : Phi = new_chunk(k);
633 189479 : e = new_chunk(k);
634 189479 : off = new_chunk(k);
635 189479 : poff = new_chunk(k);
636 : /* TODO: Surely we can work these out ahead of time? */
637 : /* h[i] is the valuation of p[i] in v */
638 189478 : h = new_chunk(k);
639 430716 : for (i = 0; i < k; ++i) {
640 241239 : h[i] = 0;
641 355151 : for (t = 1; t <= vlen; ++t)
642 282059 : if (vp[t] == L[i]) { h[i] = uel(ve, t); break; }
643 241239 : e[i] = 0;
644 241239 : off[i] = 0;
645 241239 : gel(Phi, i) = polmodular_db_getp(fdb, L[i], p);
646 : }
647 :
648 189477 : t = surface_path(roots, n[0], gel(Phi, 0), L[0], h[0], J0, NULL, p, pi);
649 189466 : if (t < n[0]) return gc_NULL(ltop); /* J0 has bad endo ring */
650 189168 : if (k == 1) { setlg(roots_, t + 1); return gc_const(av,roots_); }
651 :
652 44989 : M = new_chunk(k);
653 96247 : for (M[0] = 1, i = 1; i < k; ++i) M[i] = M[i-1] * n[i-1];
654 44989 : i = 1;
655 202067 : while (i < k) {
656 : long j, t0;
657 177925 : for (j = i + 1; j < k && ! e[j]; ++j);
658 157271 : if (j < k) {
659 63497 : if (e[i]) {
660 41181 : if (! common_nbr_pred(
661 164724 : &roots[t], roots[off[i]], gel(Phi,i), L[i],
662 41181 : roots[t - M[j]], gel(Phi, j), L[j], roots[poff[i]], p, pi)) {
663 0 : break; /* J0 has bad endo ring */
664 : }
665 22316 : } else if ( ! common_nbr_corner(
666 111580 : &roots[t], roots[off[i]], gel(Phi,i), L[i], h[i],
667 22316 : roots[t - M[j]], gel(Phi, j), L[j], roots[poff[j]], p, pi)) {
668 52 : break; /* J0 has bad endo ring */
669 : }
670 144884 : } else if ( ! next_surface_nbr(
671 375096 : &roots[t], gel(Phi,i), L[i], h[i],
672 144888 : roots[off[i]], e[i] ? &roots[poff[i]] : NULL, p, pi))
673 0 : break; /* J0 has bad endo ring */
674 157215 : if (roots[t] == roots[0]) break; /* J0 has bad endo ring */
675 :
676 157140 : poff[i] = off[i];
677 157140 : off[i] = t;
678 157140 : e[i]++;
679 179456 : for (j = i-1; j; --j) { e[j] = 0; off[j] = off[j+1]; }
680 :
681 471420 : t0 = surface_parallel_path(&roots[t], &roots[poff[i]], n[0],
682 157140 : gel(Phi, 0), L[0], gel(Phi, i), L[i], p, pi, n[0] == o[0]);
683 157146 : if (t0 < n[0]) break; /* J0 has bad endo ring */
684 :
685 : /* TODO: Do I need to check if any of the new roots is a repeat in
686 : * the case where J0 has bad endo ring? */
687 157078 : t += n[0];
688 230255 : for (i = 1; i < k && e[i] == n[i]-1; i++);
689 : }
690 44991 : if (t != N) return gc_NULL(ltop); /* J0 has wrong endo ring */
691 44796 : setlg(roots_, t + 1); return gc_const(av,roots_);
692 : }
|