Revision 1fdf9dbb5336543afda3bad4317128a6e6a1ff20 authored by Emmanuel Thomé on 10 August 2021, 06:36:21 UTC, committed by Emmanuel Thomé on 10 August 2021, 06:36:21 UTC
WIP: fix #21825 part c ; low-level code for reduce-plattice

Closes #21825

See merge request cado-nfs/cado-nfs!3
2 parent s c5b20ea + 86c11ac
Raw File
twoquadratics.c
#include "cado.h" // IWYU pragma: keep
#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <math.h>
#include "auxiliary.h"
#include "area.h"
#include "murphyE.h"
#include "mpz_vector.h"
#include "macros.h"
#include "gmp_aux.h"
#include "params.h"
#include "verbose.h"

typedef struct {
  cado_poly poly;
  mpz_t m;
  mpz_t p;  // common root is m/p mod N
  mpz_t skew;
  double E;
} cado_poly_extended_s;

typedef cado_poly_extended_s cado_poly_extended[1];
typedef cado_poly_extended_s *cado_poly_extended_ptr;

void
cado_poly_extended_init (cado_poly_extended poly)
{
  cado_poly_init (poly->poly);
  mpz_init (poly->m);
  mpz_init (poly->p);
  mpz_init (poly->skew);
  poly->E = 0.0;
}

void
cado_poly_extended_clear (cado_poly_extended poly)
{
  cado_poly_clear (poly->poly);
  mpz_clear (poly->m);
  mpz_clear (poly->p);
  mpz_clear (poly->skew);
  poly->E = 0.0;
}

void
cado_poly_set2 (cado_poly poly, mpz_poly f, mpz_poly g, mpz_t N, 
                mpz_t skew)
{
  mpz_poly_set (poly->pols[0], f);
  mpz_poly_set (poly->pols[1], g);
  mpz_set (poly->n, N);
  poly->skew = mpz_get_d (skew);
}

void
cado_poly_extended_set (cado_poly_extended poly, mpz_poly f, mpz_poly g,
                        mpz_t N, mpz_t p, mpz_t skew, double E, mpz_t m)
{
  mpz_poly_set (poly->poly->pols[0], f);
  mpz_poly_set (poly->poly->pols[1], g);
  mpz_set (poly->poly->n, N);
  poly->poly->skew = mpz_get_d (skew);

  mpz_set (poly->p, p);
  mpz_set (poly->skew, skew);
  poly->E = E;
  mpz_set(poly->m, m);
}

double
cado_poly_extended_get_E (cado_poly_extended poly)
{
  return poly->E;
}

void cado_poly_extended_print_cado_format(FILE *out, cado_poly_extended poly,
    mpz_srcptr N) {
  mpz_poly_ptr f0 = poly->poly->pols[0];
  mpz_poly_ptr f1 = poly->poly->pols[1];

  gmp_fprintf(out, "n: %Zd\n", N);
  for (int i = 0; i < 3; i++)
    gmp_fprintf(out, "c%d: %Zd\n", i, f0->coeff[i]);
  for (int i = 0; i < 3; i++)
    gmp_fprintf(out, "Y%d: %Zd\n", i, f1->coeff[i]);
  fprintf(out, "skew: %1.2f\n", L2_combined_skewness2(f0, f1, SKEWNESS_DEFAULT_PREC));
}

void
cado_poly_extended_print (FILE *out, cado_poly_extended poly, char *pre)
{
  mpz_t tmp;
  mpz_init (tmp);
  double s = poly->poly->skew;

  mpz_poly_ptr f0 = poly->poly->pols[0];
  mpz_poly_ptr f1 = poly->poly->pols[1];

  gmp_fprintf (out, "%sN = %Zd\n", pre, poly->poly->n);

  fprintf(out, "%sf0 = ", pre);
  mpz_poly_fprintf (out, f0);
  fprintf(out, "%sf1 = ", pre);
  mpz_poly_fprintf (out, f1);

  if (f0->deg != -1 && f1->deg != -1)
  {
    fprintf (out, "%sE = %e\n", pre, poly->E);
    fprintf (out, "%salpha_f0 = %.2f\n", pre, get_alpha (f0, get_alpha_bound ()));
    fprintf (out, "%salpha_f1 = %.2f\n", pre, get_alpha (f1, get_alpha_bound ()));
    fprintf (out, "%sskew_f0 = %.2f\n", pre, L2_skewness (f0, SKEWNESS_DEFAULT_PREC));
    fprintf (out, "%sskew_f1 = %.2f\n", pre, L2_skewness (f1, SKEWNESS_DEFAULT_PREC));
    gmp_fprintf (out, "%sskewness = %Zd\n", pre, poly->skew);
    fprintf (out, "%sL2_skew_norm_f0 = %.2f\n", pre, L2_lognorm (f0, s));
    fprintf (out, "%sL2_skew_norm_f1 = %.2f\n", pre, L2_lognorm (f1, s));
    gmp_fprintf (out, "%sp = %Zd\n", pre, poly->p);
    gmp_fprintf (out, "%sm = %Zd\n", pre, poly->m);
    mpz_mod (tmp, poly->m, poly->p);
    gmp_fprintf (out, "%sr = %Zd\n", pre, tmp);
  }

  mpz_clear (tmp);
}



/* Set m to the nearest integer to m0, such that
   m = r mod P, i.e. find the integer in [m-p/2,m+p/2] which is congruent to
   r modulo P.
 */
void
compute_m (mpz_t m, mpz_t m0, mpz_t r, mpz_t P)
{
  mpz_t t;
  mpz_init (t);
  mpz_tdiv_q_2exp (m, P, 1); // m = floor(p/2)
  mpz_add (m, m ,m0);        // m = m0 + floor(p/2)
  mpz_sub (t, m ,r);         // t = m0 + floor(p/2) - r
  mpz_mod (t, t, P);         // t = (m0 + floor(p/2) -r) mod P
  mpz_sub (m, m, t);         // m = m0 + floor(p/2) - t
                             // By definition, (m0 + floor(p/2) - t) = r mod P
  mpz_clear (t);
}

/* Compute maximun skewness, which in floor(N^(1/d^2)) */
void
compute_default_max_skew (mpz_t skew, mpz_t N, int d)
{
  mpz_root (skew, N, (unsigned long) d*d);
}

/* Returns the square root of N modulo P, using Tonelli-Shanks' algorithm.
   Assume P is prime.
   Returns 0 in case of error (e.g. N is not a square), else non-zero.
*/
int
mpz_msqrt (mpz_t r, mpz_t N, mpz_t P)
{
  if (mpz_cmp_ui (P, 2) == 0)
  {
    mpz_mod (r, N, P);
    return mpz_sgn(r); // Return 0 if r == 0
  }
  else if (mpz_legendre (N, P) != 1)
    return 0;
  else
  {
    mpz_t Q, n, z, c, R, t, t0, b;

    mpz_init(n);
    mpz_init_set_ui(z, 2);
    mpz_init(c);
    mpz_init(R);
    mpz_init(t);
    mpz_init(t0);
    mpz_init(b);
    mpz_init(Q);

    mpz_mod(n, N, P);

    mpz_sub_ui (Q, P, 1); // Q = P-1
    int M, S;
    for (S = 0; mpz_even_p(Q); S++)
      mpz_tdiv_q_2exp (Q, Q, 1);
    M = S;

    while (mpz_legendre(z, P) != -1)
        mpz_add_ui(z, z, 1);

    // We want c = z^Q mod P , t = n^Q mod P and R = n^((Q+1)/2) mod P
    mpz_powm (c, z, Q, P); // c = z^Q mod P
    mpz_sub_ui (Q, Q, 1);
    mpz_tdiv_q_2exp (Q, Q, 1);
    mpz_powm (R, n, Q, P); // R = n ^ ((Q-1)/2) mod P
    mpz_mul (t, R, R);
    mpz_mod (t, t, P);       // t = n^(Q-1) mod P
    mpz_mul (R, R, n);
    mpz_mod (R, R, P);       // R = n ^ ((Q+1)/2) mod P
    mpz_mul (t, t, n);
    mpz_mod (t, t, P);       // t = n^Q mod P

    while (mpz_cmp_ui (t, 1) != 0)
    {
      int i = 0;
      mpz_set(t0, t);
      while(mpz_cmp_ui(t, 1) != 0)
      {
        mpz_pow_ui(t, t, 2);
        mpz_mod(t, t, P);
        i++;
      }
      mpz_set(b, c);
      int j;
      for (j = 0; j < M - i - 1; j++)
      {
        mpz_pow_ui(b, b, 2);
        mpz_mod(b, b, P);
      }
      mpz_mul(R, R, b);
      mpz_mod(R, R, P);
      mpz_mul(c, b, b);
      mpz_mod(c, c, P);
      mpz_mul(t, t0, c);
      mpz_mod(t, t, P);
      M = i;
    }

  mpz_set (r, R);

  mpz_clear(Q);
  mpz_clear(n);
  mpz_clear(z);
  mpz_clear(c);
  mpz_clear(t);
  mpz_clear(t0);
  mpz_clear(b);
  mpz_clear(R);
  return 1;
  }
}

/* Compute f and g the two polynomials found via Montgomery's Two
   Quadratics Method, given N, P and m.
   P and N should be coprime and m^2-N should be divisible by P.
   The geometric progression is [c0, c1, c2] = [ p, m, (m^2-N)/p ]
   We start with a = [m, -p, 0] and b = [(m*t-c2)/P, -t, 1]
      where t = c2/m mod P
   We then compute a reduced basis of the lattice spanned by a and b with the
   Lagrange algorithm using the maximun possible skewness (bound by maxS)
 */
void MontgomeryTwoQuadratics (mpz_poly f, mpz_poly g, mpz_t skew, mpz_t N,
                              mpz_t P, mpz_t m, mpz_t maxS)
{
  ASSERT_ALWAYS (mpz_coprime_p(P, N));

  mpz_vector_t a, b, reduced_a, reduced_b;
  mpz_vector_init (a, 3);
  mpz_vector_init (b, 3);
  mpz_vector_init (reduced_a, 3);
  mpz_vector_init (reduced_b, 3);

  mpz_t tmp, c2, t;
  mpz_init(tmp);
  mpz_init(c2);
  mpz_init(t);

  // compute c2
  mpz_mul (tmp, m, m);
  mpz_sub (tmp, tmp, N);
  ASSERT_ALWAYS (mpz_divisible_p(tmp, P));
  mpz_divexact (c2, tmp, P);    // c2 = (m^2 - N) / P

  //compute t
  int ret = mpz_invert (tmp, m, P);
  ASSERT_ALWAYS (ret != 0);
  mpz_mul (t, tmp, c2);          // t = c2 / m mod P

  // Set vector a
  mpz_vector_setcoordinate (a, 0, m); // a[0] = m
  mpz_neg (tmp, P);
  mpz_vector_setcoordinate (a, 1, tmp); // a[1] = -P
  mpz_vector_setcoordinate_ui (a, 2, 0); // a[2] = 0

  // Set vector b
  mpz_mul (tmp, m, t);
  mpz_sub (tmp, tmp, c2);
  ASSERT_ALWAYS (mpz_divisible_p(tmp, P));
  mpz_divexact (tmp, tmp, P);
  mpz_vector_setcoordinate (b, 0, tmp); // b[0] = (m*t - c2) / P
  mpz_neg (tmp, t);
  mpz_vector_setcoordinate (b, 1, tmp); // b[1] = -t
  mpz_vector_setcoordinate_ui (b, 2, 1); // b[2] = 1

  mpz_vector_reduce_with_max_skew (reduced_a, reduced_b, skew, a, b, maxS, 2);

  mpz_vector_get_mpz_poly(f, reduced_a);
  mpz_vector_get_mpz_poly(g, reduced_b);

  mpz_clear(tmp);
  mpz_clear(c2);
  mpz_clear(t);
  mpz_vector_clear (a);
  mpz_vector_clear (b);
  mpz_vector_clear (reduced_a);
  mpz_vector_clear (reduced_b);
}


static void declare_usage(param_list pl)
{
  param_list_decl_usage(pl, "N", "input number (default c59)");
  param_list_decl_usage(pl, "minP", "Use P > minP (default 2)");
  param_list_decl_usage(pl, "maxP", "Use P <= maxP (default nextprime(minP))");
  param_list_decl_usage(pl, "skewness", "maximun skewness possible "
                                        "(default floor(N^(1/4))");
  param_list_decl_usage(pl, "v", "verbose output (print all polynomials)");
  param_list_decl_usage(pl, "q", "quiet output (print only best polynomials)");
  char str[200];
  snprintf (str, 200, "sieving area (default %.2e)", AREA);
  param_list_decl_usage(pl, "area", str);
  snprintf (str, 200, "algebraic smoothness bound (default %.2e)", BOUND_F);
  param_list_decl_usage(pl, "Bf", str);
  snprintf (str, 200, "rational smoothness bound (default %.2e)", BOUND_G);
  param_list_decl_usage(pl, "Bg", str);
  param_list_decl_usage(pl, "print", "print in cado_poly format");
  verbose_decl_usage(pl);
}

static void usage (const char *argv, param_list pl)
{
  param_list_print_usage(pl, argv, stderr);
  exit (EXIT_FAILURE);
}

int
main (int argc, char *argv[])
{
  char *argv0 = argv[0];
  int verbose = 0, quiet = 0, print = 0;

  mpz_t N, minP, maxP, max_skewness;
  mpz_init (N);
  mpz_init (minP);
  mpz_init (maxP);
  mpz_init (max_skewness);

  param_list pl;

  /* read params */
  param_list_init(pl);
  declare_usage(pl);

  param_list_configure_switch (pl, "-v", &verbose);
  param_list_configure_switch (pl, "-q", &quiet);
  param_list_configure_switch (pl, "-print", &print);

  if (argc == 1)
    usage (argv[0], pl);

  argc--,argv++;
  for ( ; argc ; )
  {
    if (param_list_update_cmdline (pl, &argc, &argv)) { continue; }
    fprintf (stderr, "Unhandled parameter %s\n", argv[0]);
    usage (argv0, pl);
  }

  if (!param_list_parse_mpz(pl, "skewness", max_skewness))
    mpz_set_ui (max_skewness, 0);
  else if (mpz_cmp_ui (max_skewness, 1) < 0)
  {
    gmp_fprintf(stderr, "Error, skewness (%Zd) should be positive\n",
                         max_skewness);
    abort();
  }

  if (param_list_parse_double (pl, "area", &area) == 0) /* no -area */
    area = AREA;
  if (param_list_parse_double (pl, "Bf", &bound_f) == 0) /* no -Bf */
    bound_f = BOUND_F;
  if (param_list_parse_double (pl, "Bg", &bound_g) == 0) /* no -Bg */
    bound_g = BOUND_G;
  if (!param_list_parse_mpz(pl, "minP", minP))
    mpz_set_ui (minP, 2);
  if (!param_list_parse_mpz(pl, "maxP", maxP))
    mpz_nextprime(maxP, minP);
  if (!param_list_parse_mpz(pl, "N", N))
    mpz_set_str (N,
        "71641520761751435455133616475667090434063332228247871795429", 10);

  if (mpz_cmp_ui (minP, 0) < 0)
  {
    gmp_fprintf(stderr, "Error, minP (%Zd) should be greater or equal to 0\n",
                        minP);
    abort();
  }
  if (mpz_cmp (maxP, minP) < 0)
  {
    gmp_fprintf(stderr, "Error, maxP (%Zd) should be greater or equal to minP "
                        "(%Zd)\n", maxP, minP);
    abort();
  }
  if (mpz_cmp_ui (N, 3) < 0)
  {
    gmp_fprintf(stderr, "Error, N (%Zd) should be greater or equal to 3\n", N);
    abort();
  }
  if (mpz_divisible_ui_p (N, 2))
  {
    gmp_fprintf(stderr, "Error, N (%Zd) should not be divisible by 2\n", N);
    abort();
  }

  if (quiet)
    verbose = -1;

  verbose_interpret_parameters(pl);
  if (param_list_warn_unused(pl))
    usage (argv0, pl);
  param_list_print_command_line (stdout, pl);

  if (verbose >= 0)
  {
    gmp_printf("### N = %Zd\n", N);
    printf("### Bf = %.1e , Bg = %.1e , area = %.1e\n", bound_f, bound_g, area);
  }

  /* Compute max_skewness: use max_skewness if max_skewness argument is greater
     than 0 and lesser than default value */
  mpz_t tmp;
  mpz_init (tmp);
  compute_default_max_skew (tmp, N, 2);
  if (mpz_cmp_ui(max_skewness, 0) == 0 || mpz_cmp(max_skewness, tmp) > 0)
    mpz_set (max_skewness, tmp);
  mpz_clear (tmp);

/* Given an integer N and bounds on P, tests all pairs of polynomials
   with P such that minP < P <= maxP and print the pair having the best
   rating, along with its skewness, norm, alpha(f), alpha(g), and the
   prime number P which gave it
 */
  cado_poly_extended best_poly, poly;
  cado_poly cur_poly;
  mpz_t P, sqrtN, r, m, skew_used;
  mpz_poly f, g;

  cado_poly_extended_init (best_poly);
  cado_poly_extended_init (poly);
  cado_poly_init (cur_poly);
  mpz_init(P);
  mpz_init(sqrtN);
  mpz_init(r);
  mpz_init(m);
  mpz_init(skew_used);
  mpz_poly_init (f, 2);
  mpz_poly_init (g, 2);

  // sqrtN = floor(sqrt(N))
  mpz_sqrt(sqrtN, N);

  mpz_nextprime (P, minP);
  while(mpz_cmp (P, maxP) <= 0)
  {
    if (mpz_kronecker(N, P) == 1)
    {
      for (unsigned int k = 0; k < 2; k++)
      {
        if (k == 0) // For the first time compute r = sqrt of N modulo P
        {
          int ret = mpz_msqrt (r, N, P);
          ASSERT_ALWAYS (ret != 0);
        }
        else // The second root mod P is P-r
          mpz_sub(r, P, r);

        if (verbose >= 0)
          gmp_printf("### P = %Zd: compute polynomials for r = %Zd\n", P, r);
        // m is the first integer >= sqrtN such that m = r (mod P)
        compute_m (m, sqrtN, r, P);
        MontgomeryTwoQuadratics (f, g, skew_used, N, P, m, max_skewness);
        cado_poly_set2 (cur_poly, f, g, N, skew_used);
        double E = MurphyE (cur_poly, bound_f, bound_g, area, MURPHY_K,
                            get_alpha_bound ());
        if(E > cado_poly_extended_get_E(best_poly))
          cado_poly_extended_set (best_poly, f, g, N, P, skew_used, E, m);
        if (verbose >= 1)
        {
          cado_poly_extended_set (poly, f, g, N, P, skew_used, E, m);
          cado_poly_extended_print (stdout, poly, "# ");
        }
      }
    }
    else if (verbose >= 0)
      gmp_printf("### P = %Zd: N is not a square modulo P, skipping it.\n", P);
    mpz_nextprime(P, P);
  }

  if (print) {
    cado_poly_extended_print_cado_format(stdout, best_poly, N);
    cado_poly_extended_print (stdout, best_poly, "# ");
  } else {
    printf("### Best polynomials found:\n");
    cado_poly_extended_print (stdout, best_poly, "");
  }

  mpz_clear(P);
  mpz_clear(sqrtN);
  mpz_clear(r);
  mpz_clear(m);

  mpz_poly_clear (f);
  mpz_poly_clear (g);

  cado_poly_clear (cur_poly);
  cado_poly_extended_clear (poly);
  cado_poly_extended_clear (best_poly);
  mpz_clear (N);
  mpz_clear (skew_used);
  mpz_clear (max_skewness);
  mpz_clear (minP);
  mpz_clear (maxP);
  param_list_clear(pl);
  return EXIT_SUCCESS;
}
back to top