Code coverage tests

This page documents the degree to which the PARI/GP source code is tested by our public test suite, distributed with the source distribution in directory src/test/. This is measured by the gcov utility; we then process gcov output using the lcov frond-end.

We test a few variants depending on Configure flags on the pari.math.u-bordeaux.fr machine (x86_64 architecture), and agregate them in the final report:

The target is to exceed 90% coverage for all mathematical modules (given that branches depending on DEBUGLEVEL or DEBUGMEM are not covered). This script is run to produce the results below.

LCOV - code coverage report
Current view: top level - basemath - volcano.c (source / functions) Coverage Total Hit
Test: PARI/GP v2.18.1 lcov report (development 31042-0fbe168e69) Lines: 98.6 % 345 340
Test Date: 2026-07-23 17:04:59 Functions: 100.0 % 28 28
Legend: Lines:     hit not hit

            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              : static GEN
      19       236576 : pcp_get_L(GEN G) { return gmael(G,1,1)+1; }
      20              : static GEN
      21       236576 : pcp_get_n(GEN G) { return gmael(G,1,2)+1; }
      22              : static GEN
      23       236576 : pcp_get_o(GEN G) { return gmael(G,1,3)+1; }
      24              : static long
      25       236576 : pcp_get_L0(GEN G) { return mael(G,2,1); }
      26              : static long
      27       236576 : pcp_get_k(GEN G) { return mael(G,2,2); }
      28              : static long
      29       236576 : pcp_get_enum_cnt(GEN G) { return mael(G,2,3); }
      30              : 
      31              : /* FIXME: Implement {ascend,descend}_volcano() in terms of the "new"
      32              :  * volcano traversal functions at the bottom of the file. */
      33              : 
      34              : /* Is j = 0 or 1728 (mod p)? */
      35              : INLINE int
      36       384573 : is_j_exceptional(ulong j, ulong p) { return j == 0 || j == 1728 % p; }
      37              : 
      38              : INLINE long
      39        85446 : node_degree(GEN phi, long L, ulong j, ulong p, ulong pi)
      40              : {
      41        85446 :   pari_sp av = avma;
      42        85446 :   long n = Flx_nbroots(Flm_Fl_polmodular_evalx(phi, L, j, p, pi), p);
      43        85446 :   return gc_long(av, n);
      44              : }
      45              : 
      46              : /* Given an array path = [j0, j1] of length 2, return the polynomial
      47              :  *
      48              :  *   \Phi_L(X, j1) / (X - j0)
      49              :  *
      50              :  * where \Phi_L(X, Y) is the modular polynomial of level L.  An error
      51              :  * is raised if X - j0 does not divide \Phi_L(X, j1) */
      52              : INLINE GEN
      53       150850 : nhbr_polynomial(ulong path[], GEN phi, ulong p, ulong pi, long L)
      54              : {
      55       150850 :   GEN modpol = Flm_Fl_polmodular_evalx(phi, L, path[0], p, pi);
      56              :   ulong rem;
      57       150850 :   GEN nhbr_pol = Flx_div_by_X_x(modpol, path[-1], p, &rem);
      58              :   /* If disc End(path[0]) <= L^2, it's possible for path[0] to appear among the
      59              :    * roots of nhbr_pol. This should have been obviated by earlier choices */
      60       150850 :   if (rem) pari_err_BUG("nhbr_polynomial: invalid preceding j");
      61       150850 :   return nhbr_pol;
      62              : }
      63              : 
      64              : /* Path is an array with space for at least max_len+1 * elements, whose first
      65              :  * and second elements are the beginning of the path.  I.e., the path starts
      66              :  *   (path[0], path[1])
      67              :  * If the result is less than max_len, then the last element of path is on the
      68              :  * floor.  If the result equals max_len, then it is unknown whether the last
      69              :  * element of path is on the floor or not */
      70              : static long
      71       296939 : extend_path(ulong path[], GEN phi, ulong p, ulong pi, long L, long max_len)
      72              : {
      73       296939 :   pari_sp av = avma;
      74       296939 :   long d = 1;
      75       382026 :   for ( ; d < max_len; d++)
      76              :   {
      77       109393 :     GEN nhbr_pol = nhbr_polynomial(path + d, phi, p, pi, L);
      78       109393 :     ulong nhbr = Flx_oneroot_pre(nhbr_pol, p, pi);
      79       109393 :     set_avma(av);
      80       109393 :     if (nhbr == p) break; /* no root: we are on the floor. */
      81        85087 :     path[d+1] = nhbr;
      82              :   }
      83       296939 :   return d;
      84              : }
      85              : 
      86              : /* This is Sutherland 2009 Algorithm Ascend (p12) */
      87              : ulong
      88       135286 : ascend_volcano(GEN phi, ulong j, ulong p, ulong pi, long level, long L,
      89              :   long depth, long steps)
      90              : {
      91       135286 :   pari_sp ltop = avma, av;
      92              :   /* path will never hold more than max_len < depth elements */
      93       135286 :   GEN path_g = cgetg(depth + 2, t_VECSMALL);
      94       135286 :   ulong *path = zv_to_ulongptr(path_g);
      95       135286 :   long max_len = depth - level;
      96       135286 :   int first_iter = 1;
      97              : 
      98       135286 :   if (steps <= 0 || max_len < 0) pari_err_BUG("ascend_volcano: bad params");
      99       135286 :   av = avma;
     100       312029 :   while (steps--)
     101              :   {
     102       135286 :     GEN nhbr_pol = first_iter? Flm_Fl_polmodular_evalx(phi, L, j, p, pi)
     103       176743 :                              : nhbr_polynomial(path+1, phi, p, pi, L);
     104       176743 :     GEN nhbrs = Flx_roots_pre(nhbr_pol, p, pi);
     105       176743 :     long nhbrs_len = lg(nhbrs)-1, i;
     106       176743 :     pari_sp btop = avma;
     107       176743 :     path[0] = j;
     108       176743 :     first_iter = 0;
     109              : 
     110       176743 :     j = nhbrs[nhbrs_len];
     111       222460 :     for (i = 1; i < nhbrs_len; i++)
     112              :     {
     113        82344 :       ulong next_j = nhbrs[i], last_j;
     114              :       long len;
     115        82344 :       if (is_j_exceptional(next_j, p))
     116              :       {
     117              :         /* Fouquet & Morain, Section 4.3, if j = 0 or 1728, then it is on the
     118              :          * surface.  So we just return it. */
     119           33 :         if (steps)
     120            0 :           pari_err_BUG("ascend_volcano: Got to the top with more steps to go!");
     121           33 :         j = next_j; break;
     122              :       }
     123        82311 :       path[1] = next_j;
     124        82311 :       len = extend_path(path, phi, p, pi, L, max_len);
     125        82311 :       last_j = path[len];
     126        82311 :       if (len == max_len
     127              :           /* Ended up on the surface */
     128        82311 :           && (is_j_exceptional(last_j, p)
     129        82311 :               || node_degree(phi, L, last_j, p, pi) > 1)) { j = next_j; break; }
     130        45717 :       set_avma(btop);
     131              :     }
     132       176743 :     path[1] = j; /* For nhbr_polynomial() at the top. */
     133              : 
     134       176743 :     max_len++; set_avma(av);
     135              :   }
     136       135286 :   return gc_long(ltop, j);
     137              : }
     138              : 
     139              : static void
     140       219907 : random_distinct_neighbours_of(ulong *nhbr1, ulong *nhbr2,
     141              :   GEN phi, ulong j, ulong p, ulong pi, long L, long must_have_two_neighbours)
     142              : {
     143       219907 :   pari_sp av = avma;
     144       219907 :   GEN modpol = Flm_Fl_polmodular_evalx(phi, L, j, p, pi);
     145              :   ulong rem;
     146       219907 :   *nhbr1 = Flx_oneroot_pre(modpol, p, pi);
     147       219907 :   if (*nhbr1 == p) pari_err_BUG("random_distinct_neighbours_of [no neighbour]");
     148       219907 :   modpol = Flx_div_by_X_x(modpol, *nhbr1, p, &rem);
     149       219907 :   *nhbr2 = Flx_oneroot_pre(modpol, p, pi);
     150       219907 :   if (must_have_two_neighbours && *nhbr2 == p)
     151            0 :     pari_err_BUG("random_distinct_neighbours_of [single neighbour]");
     152       219907 :   set_avma(av);
     153       219907 : }
     154              : 
     155              : /* This is Sutherland 2009 Algorithm Descend (p12) */
     156              : ulong
     157         2901 : descend_volcano(GEN phi, ulong j, ulong p, ulong pi,
     158              :   long level, long L, long depth, long steps)
     159              : {
     160         2901 :   pari_sp ltop = avma;
     161              :   GEN path_g;
     162              :   ulong *path;
     163              :   long max_len;
     164              : 
     165         2901 :   if (steps <= 0 || level + steps > depth) pari_err_BUG("descend_volcano");
     166         2901 :   max_len = depth - level;
     167         2901 :   path_g = cgetg(max_len + 1 + 1, t_VECSMALL);
     168         2901 :   path = zv_to_ulongptr(path_g);
     169         2901 :   path[0] = j;
     170              :   /* level = 0 means we're on the volcano surface... */
     171         2901 :   if (!level)
     172              :   {
     173              :     /* Look for any path to the floor. One of j's first three neighbours leads
     174              :      * to the floor, since at most two neighbours are on the surface. */
     175         2569 :     GEN nhbrs = Flx_roots_pre(Flm_Fl_polmodular_evalx(phi, L, j, p, pi), p, pi);
     176              :     long i;
     177         2855 :     for (i = 1; i <= 3; i++)
     178              :     {
     179              :       long len;
     180         2855 :       path[1] = nhbrs[i];
     181         2855 :       len = extend_path(path, phi, p, pi, L, max_len);
     182              :       /* If nhbrs[i] took us to the floor: */
     183         2855 :       if (len < max_len || node_degree(phi, L, path[len], p, pi) == 1) break;
     184              :     }
     185         2569 :     if (i > 3) pari_err_BUG("descend_volcano [2]");
     186              :   }
     187              :   else
     188              :   {
     189              :     ulong nhbr1, nhbr2;
     190              :     long len;
     191          332 :     random_distinct_neighbours_of(&nhbr1, &nhbr2, phi, j, p, pi, L, 1);
     192          332 :     path[1] = nhbr1;
     193          332 :     len = extend_path(path, phi, p, pi, L, max_len);
     194              :     /* If last j isn't on the floor */
     195          332 :     if (len == max_len   /* Ended up on the surface. */
     196          332 :         && (is_j_exceptional(path[len], p)
     197          280 :             || node_degree(phi, L, path[len], p, pi) != 1)) {
     198              :       /* The other neighbour leads to the floor */
     199          113 :       path[1] = nhbr2;
     200          113 :       (void) extend_path(path, phi, p, pi, L, steps);
     201              :     }
     202              :   }
     203         2901 :   return gc_ulong(ltop, path[steps]);
     204              : }
     205              : 
     206              : long
     207       219586 : j_level_in_volcano(
     208              :   GEN phi, ulong j, ulong p, ulong pi, long L, long depth)
     209              : {
     210       219586 :   pari_sp av = avma;
     211              :   GEN chunk;
     212              :   ulong *path1, *path2;
     213              :   long lvl;
     214              : 
     215              :   /* Fouquet & Morain, Section 4.3, if j = 0 or 1728 then it is on the
     216              :    * surface.  Also, if the volcano depth is zero then j has level 0 */
     217       219586 :   if (depth == 0 || is_j_exceptional(j, p)) return 0;
     218              : 
     219       219575 :   chunk = new_chunk(2 * (depth + 1));
     220       219575 :   path1 = (ulong *) &chunk[0];
     221       219575 :   path2 = (ulong *) &chunk[depth + 1];
     222       219575 :   path1[0] = path2[0] = j;
     223       219575 :   random_distinct_neighbours_of(&path1[1], &path2[1], phi, j, p, pi, L, 0);
     224       219575 :   if (path2[1] == p)
     225       113911 :     lvl = depth; /* Only one neighbour => j is on the floor => level = depth */
     226              :   else
     227              :   {
     228       105664 :     long path1_len = extend_path(path1, phi, p, pi, L, depth);
     229       105664 :     long path2_len = extend_path(path2, phi, p, pi, L, path1_len);
     230       105664 :     lvl = depth - path2_len;
     231              :   }
     232       219575 :   return gc_long(av, lvl);
     233              : }
     234              : 
     235              : INLINE GEN
     236     39917082 : Flx_remove_root(GEN f, ulong a, ulong p)
     237              : {
     238              :   ulong r;
     239     39917082 :   GEN g = Flx_div_by_X_x(f, a, p, &r);
     240     39917082 :   if (r) pari_err_BUG("Flx_remove_root");
     241     39917082 :   return g;
     242              : }
     243              : 
     244              : INLINE GEN
     245     29088630 : get_nbrs(GEN phi, long L, ulong J, const ulong *xJ, ulong p, ulong pi)
     246              : {
     247     29088630 :   pari_sp av = avma;
     248     29088630 :   GEN f = Flm_Fl_polmodular_evalx(phi, L, J, p, pi);
     249     29088630 :   if (xJ) f = Flx_remove_root(f, *xJ, p);
     250     29088630 :   return gc_upto(av, Flx_roots_pre(f, p, pi));
     251              : }
     252              : 
     253              : /* Return a path of length n along the surface of an L-volcano of height h
     254              :  * starting from surface node j0.  Assumes (D|L) = 1 where D = disc End(j0).
     255              :  *
     256              :  * Actually, if j0's endomorphism ring is a suborder, we return the
     257              :  * corresponding shorter path. W must hold space for n + h nodes.
     258              :  *
     259              :  * TODO: have two versions of this function: one that assumes J has the correct
     260              :  * endomorphism ring (hence avoiding several branches in the inner loop) and a
     261              :  * second that does not and accordingly checks for repetitions */
     262              : static long
     263       231294 : surface_path(
     264              :   ulong W[],
     265              :   long n, GEN phi, long L, long h, ulong J, const ulong *nJ, ulong p, ulong pi)
     266              : {
     267       231294 :   pari_sp av = avma, bv;
     268              :   GEN T, v;
     269              :   long j, k, w, x;
     270              :   ulong W0;
     271              : 
     272       231294 :   W[0] = W0 = J;
     273       231294 :   if (n == 1) return 1;
     274              : 
     275       231294 :   T = cgetg(h+2, t_VEC); bv = avma;
     276       231294 :   v = get_nbrs(phi, L, J, nJ, p, pi);
     277              :   /* Insert known neighbour first */
     278       231294 :   if (nJ) v = gc_upto(bv, vecsmall_prepend(v, *nJ));
     279       231294 :   gel(T,1) = v; k = lg(v)-1;
     280              : 
     281       231294 :   switch (k) {
     282            0 :   case 0: pari_err_BUG("surface_path"); /* We must always have neighbours */
     283         8750 :   case 1:
     284              :     /* If volcano is not flat, then we must have more than one neighbour */
     285         8750 :     if (h) pari_err_BUG("surface_path");
     286         8750 :     W[1] = uel(v, 1);
     287         8750 :     set_avma(av);
     288              :     /* Check for bad endo ring */
     289         8750 :     if (W[1] == W[0]) return 1;
     290         8563 :     return 2;
     291        24056 :   case 2:
     292              :     /* If L=2 the only way we can have 2 neighbours is if we have a double root
     293              :      * which can only happen for |D| <= 16 (Thm 2.2 of Fouquet-Morain)
     294              :      * and if it does we must have a 2-cycle. Happens for D=-15. */
     295        24056 :     if (L == 2)
     296              :     { /* The double root is the neighbour on the surface, with exactly one
     297              :        * neighbour other than J; the other neighbour of J has either 0 or 2
     298              :        * neighbours that are not J */
     299           84 :       GEN u = get_nbrs(phi, L, uel(v, 1), &J, p, pi);
     300           84 :       long n = lg(u)-1 - !!vecsmall_isin(u, J);
     301           84 :       W[1] = n == 1 ? uel(v,1) : uel(v,2);
     302           84 :       return gc_long(av, 2);
     303              :     }
     304              :     /* Volcano is not flat but found only 2 neighbours for the surface node J */
     305        23972 :     if (h) pari_err_BUG("surface_path");
     306              : 
     307        23972 :     W[1] = uel(v,1); /* TODO: Can we use the other root uel(v,2) somehow? */
     308      4237202 :     for (w = 2; w < n; w++)
     309              :     {
     310      4213746 :       v = get_nbrs(phi, L, W[w-1], &W[w-2], p, pi);
     311              :       /* A flat volcano must have exactly one non-previous neighbour */
     312      4213746 :       if (lg(v) != 2) pari_err_BUG("surface_path");
     313      4213746 :       W[w] = uel(v, 1);
     314              :       /* Detect cycle in case J doesn't have the right endo ring. */
     315      4213746 :       set_avma(av); if (W[w] == W0) return w;
     316              :     }
     317        23456 :     return gc_long(av, n);
     318              :   }
     319       198488 :   if (!h) pari_err_BUG("surface_path"); /* Can't have a flat volcano if k > 2 */
     320              : 
     321              :   /* At this point, each surface node has L+1 distinct neighbours, 2 of which
     322              :    * are on the surface */
     323       198488 :   w = 1;
     324      8043287 :   for (x = 0;; x++)
     325              :   {
     326              :     /* Get next neighbour of last known surface node to attempt to
     327              :      * extend the path. */
     328      8043287 :     W[w] = umael(T, ((w-1) % h) + 1, x + 1);
     329              : 
     330              :     /* Detect cycle in case the given J didn't have the right endo ring */
     331      8043287 :     if (W[w] == W0) return gc_long(av,w);
     332              : 
     333              :     /* If we have to test the last neighbour, we know it's on the
     334              :      * surface, and if we're done there's no need to extend. */
     335      8043256 :     if (x == k-1 && w == n-1) return gc_long(av,n);
     336              : 
     337              :     /* Walk forward until we hit the floor or finish. */
     338              :     /* NB: We don't keep the stack clean here; usage is in the order of Lh,
     339              :      * i.e. L roots for each level of the volcano of height h. */
     340      7912195 :     for (j = w;;)
     341     16517617 :     {
     342              :       long m;
     343              :       /* We must get 0 or L neighbours here. */
     344     24429812 :       v = get_nbrs(phi, L, W[j], &W[j-1], p, pi);
     345     24429812 :       m = lg(v)-1;
     346     24429812 :       if (!m) {
     347              :         /* We hit the floor: save the neighbours of W[w-1] and dump the rest */
     348      7844799 :         GEN nbrs = gel(T, ((w-1) % h) + 1);
     349      7844799 :         gel(T, ((w-1) % h) + 1) = gc_upto(bv, nbrs);
     350      7844799 :         break;
     351              :       }
     352     16585013 :       if (m != L) pari_err_BUG("surface_path");
     353              : 
     354     16585013 :       gel(T, (j % h) + 1) = v;
     355              : 
     356     16585013 :       W[++j] = uel(v, 1);
     357              :       /* If we have our path by h nodes, we know W[w] is on the surface */
     358     16585013 :       if (j == w + h) {
     359     15501939 :         ++w;
     360              :         /* Detect cycle in case the given J didn't have the right endo ring */
     361     15501939 :         if (W[w] == W0) return gc_long(av,w);
     362     15470706 :         x = 0; k = L;
     363              :       }
     364     16553780 :       if (w == n) return gc_long(av,w);
     365              :     }
     366              :   }
     367              : }
     368              : 
     369              : long
     370       157278 : next_surface_nbr(
     371              :   ulong *nJ,
     372              :   GEN phi, long L, long h, ulong J, const ulong *pJ, ulong p, ulong pi)
     373              : {
     374       157278 :   pari_sp av = avma, bv;
     375              :   GEN S;
     376              :   ulong *P;
     377              :   long i, k;
     378              : 
     379       157278 :   S = get_nbrs(phi, L, J, pJ, p, pi); k = lg(S)-1;
     380              :   /* If there is a double root and pJ is set, then k will be zero. */
     381       157278 :   if (!k) return gc_long(av,0);
     382       157278 :   if (k == 1 || ( ! pJ && k == 2)) { *nJ = uel(S, 1); return gc_long(av,1); }
     383        23938 :   if (!h) pari_err_BUG("next_surface_nbr");
     384              : 
     385        23938 :   P = (ulong *) new_chunk(h + 1); bv = avma;
     386        23938 :   P[0] = J;
     387        51177 :   for (i = 0; i < k; i++)
     388              :   {
     389              :     long j;
     390        51177 :     P[1] = uel(S, i + 1);
     391        80354 :     for (j = 1; j <= h; j++)
     392              :     {
     393        56416 :       GEN T = get_nbrs(phi, L, P[j], &P[j - 1], p, pi);
     394        56416 :       if (lg(T) == 1) break;
     395        29177 :       P[j + 1] = uel(T, 1);
     396              :     }
     397        51177 :     if (j < h) pari_err_BUG("next_surface_nbr");
     398        51177 :     set_avma(bv); if (j > h) break;
     399              :   }
     400              :   /* TODO: We could save one get_nbrs call by iterating from i up to k-1 and
     401              :    * assume that the last (kth) nbr is the one we want. For now we're careful
     402              :    * and check that this last nbr really is on the surface */
     403        23938 :   if (i == k) pari_err_BUG("next_surf_nbr");
     404        23938 :   *nJ = uel(S, i+1); return gc_long(av,1);
     405              : }
     406              : 
     407              : /* Return the number of distinct neighbours (1 or 2) */
     408              : INLINE long
     409       244052 : common_nbr(ulong *nbr,
     410              :   ulong J1, GEN Phi1, long L1,
     411              :   ulong J2, GEN Phi2, long L2, ulong p, ulong pi)
     412              : {
     413       244052 :   pari_sp av = avma;
     414              :   GEN d, f, g, r;
     415              :   long rlen;
     416              : 
     417       244052 :   g = Flm_Fl_polmodular_evalx(Phi1, L1, J1, p, pi);
     418       244052 :   f = Flm_Fl_polmodular_evalx(Phi2, L2, J2, p, pi);
     419       244052 :   d = Flx_gcd(f, g, p);
     420       244052 :   if (degpol(d) == 1) { *nbr = Flx_deg1_root(d, p); return gc_long(av,1); }
     421         1274 :   if (degpol(d) != 2) pari_err_BUG("common_neighbour");
     422         1274 :   r = Flx_roots_pre(d, p, pi);
     423         1274 :   rlen = lg(r)-1;
     424         1274 :   if (!rlen) pari_err_BUG("common_neighbour");
     425              :   /* rlen is 1 or 2 depending on whether the root is unique or not. */
     426         1274 :   nbr[0] = uel(r, 1);
     427         1274 :   nbr[1] = uel(r, rlen); return gc_long(av,rlen);
     428              : }
     429              : 
     430              : /* Return gcd(Phi1(X,J1)/(X - J0), Phi2(X,J2)). Not stack clean. */
     431              : INLINE GEN
     432        44126 : common_nbr_pred_poly(
     433              :   ulong J1, GEN Phi1, long L1,
     434              :   ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
     435              : {
     436              :   GEN f, g;
     437              : 
     438        44126 :   g = Flm_Fl_polmodular_evalx(Phi1, L1, J1, p, pi);
     439        44126 :   g = Flx_remove_root(g, J0, p);
     440        44126 :   f = Flm_Fl_polmodular_evalx(Phi2, L2, J2, p, pi);
     441        44126 :   return Flx_gcd(f, g, p);
     442              : }
     443              : 
     444              : /* Find common neighbour of J1 and J2, where J0 is an L1 predecessor of J1.
     445              :  * Return 1 if successful, 0 if not. */
     446              : INLINE int
     447        43031 : common_nbr_pred(ulong *nbr,
     448              :   ulong J1, GEN Phi1, long L1,
     449              :   ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
     450              : {
     451        43031 :   pari_sp av = avma;
     452        43031 :   GEN d = common_nbr_pred_poly(J1, Phi1, L1, J2, Phi2, L2, J0, p, pi);
     453        43031 :   int res = (degpol(d) == 1);
     454        43031 :   if (res) *nbr = Flx_deg1_root(d, p);
     455        43031 :   return gc_bool(av,res);
     456              : }
     457              : 
     458              : INLINE long
     459         1095 : common_nbr_verify(ulong *nbr,
     460              :   ulong J1, GEN Phi1, long L1,
     461              :   ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
     462              : {
     463         1095 :   pari_sp av = avma;
     464         1095 :   GEN d = common_nbr_pred_poly(J1, Phi1, L1, J2, Phi2, L2, J0, p, pi);
     465              : 
     466         1095 :   if (!degpol(d)) return gc_long(av,0);
     467          409 :   if (degpol(d) > 1) pari_err_BUG("common_neighbour_verify");
     468          409 :   *nbr = Flx_deg1_root(d, p);
     469          409 :   return gc_long(av,1);
     470              : }
     471              : 
     472              : INLINE ulong
     473          558 : Flm_Fl_polmodular_evalxy(GEN Phi, long L, ulong x, ulong y, ulong p, ulong pi)
     474              : {
     475          558 :   pari_sp av = avma;
     476          558 :   GEN f = Flm_Fl_polmodular_evalx(Phi, L, x, p, pi);
     477          558 :   return gc_ulong(av, Flx_eval_pre(f, y, p, pi));
     478              : }
     479              : 
     480              : /* Find a common L1-neighbor of J1 and L2-neighbor of J2, given J0 an
     481              :  * L2-neighbor of J1 and an L1-neighbor of J2. Return 1 if successful, 0
     482              :  * otherwise. Will only fail if initial J-invariant had the wrong endo ring */
     483              : INLINE int
     484        37349 : common_nbr_corner(ulong *nbr,
     485              :   ulong J1, GEN Phi1, long L1, long h1,
     486              :   ulong J2, GEN Phi2, long L2, ulong J0, ulong p, ulong pi)
     487              : {
     488              :   ulong nbrs[2];
     489        37349 :   if (common_nbr(nbrs, J1,Phi1,L1, J2,Phi2,L2, p, pi) == 2)
     490              :   {
     491              :     ulong nJ1, nJ2;
     492          712 :     if (!next_surface_nbr(&nJ2, Phi1, L1, h1, J2, &J0, p, pi) ||
     493          416 :         !next_surface_nbr(&nJ1, Phi1, L1, h1, nbrs[0], &J1, p, pi)) return 0;
     494              : 
     495          356 :     if (Flm_Fl_polmodular_evalxy(Phi2, L2, nJ1, nJ2, p, pi))
     496          154 :       nbrs[0] = nbrs[1];
     497          404 :     else if (!next_surface_nbr(&nJ1, Phi1, L1, h1, nbrs[1], &J1, p, pi) ||
     498          262 :              !Flm_Fl_polmodular_evalxy(Phi2, L2, nJ1, nJ2, p, pi)) return 0;
     499              :   }
     500        37289 :   *nbr = nbrs[0]; return 1;
     501              : }
     502              : 
     503              : /* Enumerate a surface L1-cycle using gcds with Phi_L2, where c_L2=c_L1^e and
     504              :  * |c_L1|=n, where c_a is the class of the pos def reduced primeform <a,b,c>.
     505              :  * Assumes n > e > 1 and roots[0],...,roots[e-1] are already present in W */
     506              : static long
     507        98247 : surface_gcd_cycle(
     508              :   ulong W[], ulong V[], long n,
     509              :   GEN Phi1, long L1, GEN Phi2, long L2, long e, ulong p, ulong pi)
     510              : {
     511        98247 :   pari_sp av = avma;
     512              :   long i1, i2, j1, j2;
     513              : 
     514        98247 :   i1 = j2 = 0;
     515        98247 :   i2 = j1 = e - 1;
     516              :   /* If W != V we assume V actually points to an L2-isogenous parallel L1-path.
     517              :    * e should be 2 in this case */
     518        98247 :   if (W != V) { i1 = j1+1; i2 = n-1; }
     519              :   do {
     520              :     ulong t0, t1, t2, h10, h11, h20, h21;
     521              :     long k;
     522              :     GEN f, g, h1, h2;
     523              : 
     524      5555620 :     f = Flm_Fl_polmodular_evalx(Phi2, L2, V[i1], p, pi);
     525      5555620 :     g = Flm_Fl_polmodular_evalx(Phi1, L1, W[j1], p, pi);
     526      5555620 :     g = Flx_remove_root(g, W[j1 - 1], p);
     527      5555620 :     h1 = Flx_gcd(f, g, p);
     528      5555620 :     if (degpol(h1) != 1) break; /* Error */
     529      5555620 :     h11 = Flx_coeff(h1, 1);
     530      5555620 :     h10 = Flx_coeff(h1, 0); set_avma(av);
     531              : 
     532      5555620 :     f = Flm_Fl_polmodular_evalx(Phi2, L2, V[i2], p, pi);
     533      5555620 :     g = Flm_Fl_polmodular_evalx(Phi1, L1, W[j2], p, pi);
     534      5555620 :     k = j2 + 1;
     535      5555620 :     if (k == n) k = 0;
     536      5555620 :     g = Flx_remove_root(g, W[k], p);
     537      5555620 :     h2 = Flx_gcd(f, g, p);
     538      5555620 :     if (degpol(h2) != 1) break; /* Error */
     539      5555620 :     h21 = Flx_coeff(h2, 1);
     540      5555620 :     h20 = Flx_coeff(h2, 0); set_avma(av);
     541              : 
     542      5555620 :     i1++; i2--; if (i2 < 0) i2 = n-1;
     543      5555620 :     j1++; j2--; if (j2 < 0) j2 = n-1;
     544              : 
     545      5555620 :     t0 = Fl_mul_pre(h11, h21, p, pi);
     546      5555620 :     t1 = Fl_inv(t0, p);
     547      5555620 :     t0 = Fl_mul_pre(t1, h21, p, pi);
     548      5555620 :     t2 = Fl_mul_pre(t0, h10, p, pi);
     549      5555620 :     W[j1] = Fl_neg(t2, p);
     550      5555620 :     t0 = Fl_mul_pre(t1, h11, p, pi);
     551      5555620 :     t2 = Fl_mul_pre(t0, h20, p, pi);
     552      5555620 :     W[j2] = Fl_neg(t2, p);
     553      5555620 :   } while (j2 > j1 + 1);
     554              :   /* Usually the loop exits when j2 = j1 + 1, in which case we return n.
     555              :    * If we break early because of an error, then (j2 - (j1+1)) > 0 is the
     556              :    * number of elements we haven't calculated yet, and we return n minus that
     557              :    * quantity */
     558        98247 :   return gc_long(av, n - j2 + j1 + 1);
     559              : }
     560              : 
     561              : static long
     562         1072 : surface_gcd_path(
     563              :   ulong W[], ulong V[], long n,
     564              :   GEN Phi1, long L1, GEN Phi2, long L2, long e, ulong p, ulong pi)
     565              : {
     566         1072 :   pari_sp av = avma;
     567              :   long i, j;
     568              : 
     569         1072 :   i = 0; j = e;
     570              :   /* If W != V then assume V actually points to a L2-isogenous
     571              :    * parallel L1-path.  e should be 2 in this case */
     572         1072 :   if (W != V) i = j;
     573         3826 :   while (j < n)
     574              :   {
     575              :     GEN f, g, d;
     576              : 
     577         2754 :     f = Flm_Fl_polmodular_evalx(Phi2, L2, V[i], p, pi);
     578         2754 :     g = Flm_Fl_polmodular_evalx(Phi1, L1, W[j - 1], p, pi);
     579         2754 :     g = Flx_remove_root(g, W[j - 2], p);
     580         2754 :     d = Flx_gcd(f, g, p);
     581         2754 :     if (degpol(d) != 1) break; /* Error */
     582         2754 :     W[j] = Flx_deg1_root(d, p);
     583         2754 :     i++; j++; set_avma(av);
     584              :   }
     585         1072 :   return gc_long(av, j);
     586              : }
     587              : 
     588              : /* Given a path V of length n on an L1-volcano, and W[0] L2-isogenous to V[0],
     589              :  * extends the path W to length n on an L1-volcano, with W[i] L2-isogenous
     590              :  * to V[i]. Uses gcds unless L2 is too large to make it helpful. Always uses
     591              :  * GCD to get W[1] to ensure consistent orientation.
     592              :  *
     593              :  * Returns the new length of W. This will almost always be n, but could be
     594              :  * lower if V was started with a J-invariant with bad endomorphism ring */
     595              : INLINE long
     596       206703 : surface_parallel_path(
     597              :   ulong W[], ulong V[], long n,
     598              :   GEN Phi1, long L1, GEN Phi2, long L2, ulong p, ulong pi, long cycle)
     599              : {
     600              :   ulong W2, nbrs[2];
     601       206703 :   if (common_nbr(nbrs, W[0], Phi1, L1, V[1], Phi2, L2, p, pi) == 2)
     602              :   {
     603          722 :     if (n <= 2) return 1; /* Error: Two choices with n = 2; ambiguous */
     604          722 :     if (!common_nbr_verify(&W2,nbrs[0], Phi1,L1,V[2], Phi2,L2,W[0], p,pi))
     605          349 :       nbrs[0] = nbrs[1]; /* nbrs[1] must be the correct choice */
     606          373 :     else if (common_nbr_verify(&W2,nbrs[1], Phi1,L1,V[2], Phi2,L2,W[0], p,pi))
     607           36 :       return 1; /* Error: Both paths extend successfully */
     608              :   }
     609       206667 :   W[1] = nbrs[0];
     610       206667 :   if (n <= 2) return n;
     611        98247 :   return cycle? surface_gcd_cycle(W, V, n, Phi1, L1, Phi2, L2, 2, p, pi)
     612       197566 :               : surface_gcd_path (W, V, n, Phi1, L1, Phi2, L2, 2, p, pi);
     613              : }
     614              : 
     615              : GEN
     616       236576 : enum_roots(ulong J0, norm_eqn_t ne, GEN fdb, GEN G, GEN vshape)
     617              : { /* MAX_HEIGHT >= max_{p,n} val_p(n) where p and n are ulongs */
     618              :   enum { MAX_HEIGHT = BITS_IN_LONG };
     619       236576 :   pari_sp av, ltop = avma;
     620       236576 :   long s = !!pcp_get_L0(G);
     621       236576 :   long *n = pcp_get_n(G)+s, *L = pcp_get_L(G)+s, *o = pcp_get_o(G)+s, k = pcp_get_k(G)-s;
     622       236576 :   long i, t, vlen, *e, *h, *off, *poff, *M, N = pcp_get_enum_cnt(G);
     623       236576 :   ulong p = ne->p, pi = ne->pi, *roots;
     624              :   GEN Phi, vp, ve, roots_;
     625              : 
     626       236576 :   if (!k) return mkvecsmall(J0);
     627              : 
     628       231294 :   roots_ = cgetg(N + MAX_HEIGHT, t_VECSMALL);
     629       231294 :   roots = zv_to_ulongptr(roots_);
     630       231294 :   av = avma;
     631              : 
     632       231294 :   if (!vshape) vshape = factoru(ne->v);
     633       231294 :   vp = gel(vshape, 1); vlen = lg(vp)-1;
     634       231294 :   ve = gel(vshape, 2);
     635              : 
     636       231294 :   Phi = new_chunk(k);
     637       231294 :   e = new_chunk(k);
     638       231294 :   off = new_chunk(k);
     639       231294 :   poff = new_chunk(k);
     640              :   /* TODO: Surely we can work these out ahead of time? */
     641              :   /* h[i] is the valuation of p[i] in v */
     642       231294 :   h = new_chunk(k);
     643       534817 :   for (i = 0; i < k; ++i) {
     644       303523 :     h[i] = 0;
     645       447959 :     for (t = 1; t <= vlen; ++t)
     646       351966 :       if (vp[t] == L[i]) { h[i] = uel(ve, t); break; }
     647       303523 :     e[i] = 0;
     648       303523 :     off[i] = 0;
     649       303523 :     gel(Phi, i) = polmodular_db_getp(fdb, L[i], p);
     650              :   }
     651              : 
     652       231294 :   t = surface_path(roots, n[0], gel(Phi, 0), L[0], h[0], J0, NULL, p, pi);
     653       231294 :   if (t < n[0]) return gc_NULL(ltop); /* J0 has bad endo ring */
     654       230543 :   if (k == 1) { setlg(roots_, t + 1); return gc_const(av,roots_); }
     655              : 
     656        59543 :   M = new_chunk(k);
     657       130469 :   for (M[0] = 1, i = 1; i < k; ++i) M[i] = M[i-1] * n[i-1];
     658        59543 :   i = 1;
     659       266210 :   while (i < k) {
     660              :     long j, t0;
     661       233532 :     for (j = i + 1; j < k && ! e[j]; ++j);
     662       206810 :     if (j < k) {
     663        80380 :       if (e[i]) {
     664        43031 :         if (! common_nbr_pred(
     665        43031 :               &roots[t], roots[off[i]], gel(Phi,i), L[i],
     666        43031 :               roots[t - M[j]], gel(Phi, j), L[j], roots[poff[i]], p, pi)) {
     667            0 :           break; /* J0 has bad endo ring */
     668              :         }
     669        37349 :       } else if ( ! common_nbr_corner(
     670        37349 :             &roots[t], roots[off[i]], gel(Phi,i), L[i], h[i],
     671        37349 :             roots[t - M[j]], gel(Phi, j), L[j], roots[poff[j]], p, pi)) {
     672           60 :         break; /* J0 has bad endo ring */
     673              :       }
     674       197246 :     } else if ( ! next_surface_nbr(
     675       126430 :           &roots[t], gel(Phi,i), L[i], h[i],
     676       197246 :           roots[off[i]], e[i] ? &roots[poff[i]] : NULL, p, pi))
     677            0 :       break; /* J0 has bad endo ring */
     678       206750 :     if (roots[t] == roots[0]) break; /* J0 has bad endo ring */
     679              : 
     680       206703 :     poff[i] = off[i];
     681       206703 :     off[i] = t;
     682       206703 :     e[i]++;
     683       244052 :     for (j = i-1; j; --j) { e[j] = 0; off[j] = off[j+1]; }
     684              : 
     685       206703 :     t0 = surface_parallel_path(&roots[t], &roots[poff[i]], n[0],
     686       206703 :         gel(Phi, 0), L[0], gel(Phi, i), L[i], p, pi, n[0] == o[0]);
     687       206703 :     if (t0 < n[0]) break; /* J0 has bad endo ring */
     688              : 
     689              :     /* TODO: Do I need to check if any of the new roots is a repeat in
     690              :      * the case where J0 has bad endo ring? */
     691       206667 :     t += n[0];
     692       314616 :     for (i = 1; i < k && e[i] == n[i]-1; i++);
     693              :   }
     694        59543 :   if (t != N) return gc_NULL(ltop); /* J0 has wrong endo ring */
     695        59400 :   setlg(roots_, t + 1); return gc_const(av,roots_);
     696              : }
        

Generated by: LCOV version 2.0-1