x; y; t;

ellhyperellipticpolynomials(E, x = 'x) =
{ my (one = E.j^0);
  x *= one;
  return([x^3 + E.a2 * x^2 + E.a4 * x + E.a6, E.a1 * x + E.a3]);
}

elldefiningequation(E, x = 'x, y = 'y) =
{ my (one = E.j^0);
  x *= one;
  y *= one;
  my (eqns = ellhyperellipticpolynomials(E, x));
  return(y^2 + eqns[2]*y - eqns[1]);
}

ellbasechar(E) = iferr(E.p, unused_param, 0);

ffone(p, n = 1, v = 't) = ffgen(p^n, v)^0;

num_isog(E, isog) =
{ my([x,y,d] = isog);
  my(d2 = d^2, d3 = d2*d, d4 = d2^2, d6 = d3^2);
  ((x + E.a2*d2)*x + E.a4*d4)*x + E.a6*d6 - y*(y + E.a1*d*x + d3*E.a3);
}

isog_satisfies_eqns(E, F, isog) =
{
  if (Mod(num_isog(F,isog), elldefiningequation(E)) != 0,
    error("Isogeny polynomials don't satisfy the curve equations"));
}

kernel_poly_from_generator(E, P, ord = 0, x = 'x, check = 1) =
{
  if (check && ! ellisoncurve(E, P),
      error("Given point is not on given curve."));
  if (!ord, ord = ellorder(E, P));

  my (one = E.j^0, rP = P, res = Pol([one], x));
  for (r = 1, ord \ 2,
       res *= x - rP[1];
       rP = elladd(E, rP, P));
  res;
}


check_ker_pol_from_gen() =
{
  my (x = 'x, one = ffone(101));
  my (data = [
              [1, [1, 1], [0], 1, Pol([1], x)],
              [one, [37, 42], [85, 0], 2, x + 16],
              [one, [37, 42], [89, 71], 3, x + 12],
              [one, [37, 42], [21, 9], 5, x^2 + 12*x + 14],
              [one, [37, 42], [58, 59], 18, x^9 + 100*x^8 + 68*x^7 + 59*x^6 + 49*x^5 + 86*x^4 + 98*x^3 + 12*x^2 + 70*x + 56]
             ]);
  for (i = 1, #data,
    my ([rg_one, ainvs, pt, ord, expected] = data[i],
        E = ellinit(ainvs * rg_one));
    pt *= rg_one;
    my (res = kernel_poly_from_generator(E, pt, ord, x));
    if (res != expected || Mod(elldivpol(E, ord, x), res) != 0,
        error("kernel polynomial is incorrect")));
}

check_data(E, P, ord) = {
  my (p = ellbasechar(E), M = [7, 13, 3, 4]);
  if (p != 0,
    my (n = (E.j).f);
    t = ffgen(p^n, 't);
    if (
      p == 2,
      M = [t, t+1, t, 1] * t^0,

      p == 3,
      M = [t, t+1, 2*t, t+2] * t^0));
  E = ellchangecurve(E, M);
  P = ellchangepoint(P, M);
  if (!ellisoncurve(E, P) || ellorder(E, P) != ord,
      error("Broken data: ", ord, "-torsion"));
  my (F, G, FF, GG, ker);
  F = ellisogeny(E, P);
  FF = ellisogeny(E, P, 1);
  if (F[1] != FF,
      error("Got different curve when only computing image"));
  ker = kernel_poly_from_generator(E, P, ord, 'x);
  G = ellisogeny(E, ker);
  GG = ellisogeny(E, ker, 1);
  if (G[1] != GG,
      error("Got different curve when only computing image"));
  if (F[1][1..5] != G[1][1..5],
      error("Different curves obtained for same kernel"));
  if (F[2] != G[2],
      error("Different isogenies obtained for same kernel"));
  isog_satisfies_eqns(E, F[1], F[2]);
}

tatecrv(b, c, p = 0, n = 1) =
{ my (one = if (p == 0, 1, ffone(p, n)));
  ellinit([1 - c, -b, -b, 0, 0] * one);
}

do_tate(b, c, ord, p = 0, n = 1) =
{ my (E, P = [0, 0], t, M = Vec([1,0,0,1]));
  E = tatecrv(b, c, p, n);
  check_data(E, P, ord);
}

check_apply(E, ker, P, fP) =
{ my ([F, f] = ellisogeny(E, ker));
  if (ellisogenyapply(f, P) != fP, error("Wrong image of point"));
}

check_compose() =
{ my (one = ffone(101),
      E = ellinit([6, 53, 85, 32, 34] * one),
      P = [84, 71] * one, \\ order 5
      [F, f] = ellisogeny(E, P),
      Q = [89, 44] * one, \\ order 2
      [G, g] = ellisogeny(ellinit(F), Q),
      gof = ellisogenyapply(g, f));
  isog_satisfies_eqns(E, ellinit(G), gof);
}

check_prio_err(E, ker, var1, var2, tst, msg) =
{
  my (got_err = 0);
  iferr(ellisogeny(E, ker, 0, var1, var2),
        err, got_err = 1,
        errname(err) == "e_PRIORITY"
          && variable(component(err, 2)) == tst);
  if (! got_err, error(msg));
}

check_errs() =
{ my (E = ellinit([8, 5]), P = [0, 0], got_err = 0);
  \\ ellisoncurve(E, P) = 0
  iferr(ellisogeny(E, P),
        err, got_err = 1,
        errname(err) == "e_DOMAIN"
          && component(err, 4) == E && component(err, 5) == P);
  if (! got_err, error("No error when P not on E"));

  my (var1 = 'var1, var2 = 'var2);
  check_prio_err(E, P, var2, var1, 'var2, "No error with bad variable order");

  iferr(ellisogeny(E, "banana bread"),
        err, got_err = 1,
        errname(err) == "e_TYPE"
          && component(err, 2) == "banana bread");
  if (! got_err, error("No error with bad kernel type"));

  check_prio_err(E, ['pvx, 0], 'x, 'g1, 'pvx,
                 "No error with bad x-point base field variable order");
  check_prio_err(E, [0, 'pvy], 'x, 'g2, 'pvy,
                 "No error with bad y-point base field variable order");
  check_prio_err(E, 'x * 'asdf, 'x, 'g3, 'asdf,
                 "No error with bad kernel variable order");
  check_prio_err(ellinit(['a4, 'a6]), [0], 'x, 'g4, 'a4,
                 "No error with bad j-invariant variable order");

  my (t = ffgen(2^2, 't),
      one = t^0,
      E = ellinit([t, t + 1, 0, t, t] * one),
      div2 = elldivpol(E, 2));
  div2 /= polcoeff(div2, poldegree(div2));
  iferr(ellisogeny(E, div2),
        err, got_err = 1,
        errname(err) == "e_DOMAIN"
          && component(err, 4) == E && component(err, 5) == div2);
  if (! got_err,
      error("No error when quotienting E by E[2] in char 2"));
}

check_ker_pol_from_gen();
check_compose();
check_errs();
P = [0, 0];
E = ellinit([0, 3, 0, 7, 0]);
\\ Doing "1-torsion"
check_data(E, [0], 1);
check_apply(E, [0], P, P);
check_apply(E, P, [0], [0]);

\\ Doing 2-torsion
check_data(E, P, 2);
one = ffone(1009);
E = ellinit([0, 3, 0, 7, 0] * one);
check_data(E, [0], 1);
check_apply(E, [0], P, P);
check_data(E, P * one, 2);
check_apply(E, P, P, [0]);
in = [149, 125] * one; out = [833, 506] * one;
check_apply(E, P, in, out);
check_apply(E, P, [0], [0]);

\\ Doing 3-torsion...
E = ellinit([1, 0, 1, 0, 0]);
check_data(E, P, 3);
E = ellinit([1, 0, 1, 0, 0] * ffone(1009));
check_data(E, P, 3);

t = ffgen(2^4, 't); one = t^0;
E = ellinit([t^3+t^2+1,t^3+t+1,t^3+t^2+t+1,t^3+t+1,t^3+t+1]*one);
Q = [t^2+t+1,t^3+t^2+1]*one;
check_data(E, Q, 3);
Q = [t^2+t,t^3+t^2+1]*one;
check_data(E, Q, 2);
Q = [t^3+t,t^3+t^2];
check_data(E, Q, 6);

t = ffgen(2^5, 't);
E = ellinit([t^3+t^2+1,t^4+t^3+t,t^4+1,t^3+1,t^2+t]);
Q = [t^4+t^3+t^2,t^3+t^2];
check_data(E, Q, 3);

t = ffgen(3^2, 't);
one = t^0;
Q = [t, 1] * one;
E = ellinit([2,2*t,t+2,2*t,2*t+1]*one);
check_data(E, Q, 3);

\\ Quotient by full 2-torsion.  Arbitrary E here; just need char(k) != 2.
E = ellinit([0, 3, 0, 7, 0] * ffone(1009));
ker = elldivpol(E, 2) * E.j^0;
F = ellisogeny(E, ker);
FF = ellisogeny(E, ker, 1);
if (F[1] != FF, error("Got different curve when only computing image"));
isog_satisfies_eqns(E, F[1], F[2]);

\\ See Kubert 1976 for why all this works.  Data was selected randomly.
\\ Doing 4-torsion...
b = -128/7; c = 0;
do_tate(b, c, 4);

\\ Doing 5-torsion...
b = 121/13; c = b;
do_tate(b, c, 5);
do_tate(b, c, 5, 2, 7);

\\ Doing 6-torsion...
c = -7/2; b = c + c^2;
do_tate(b, c, 6);
do_tate(b, c, 6, 3, 3);

\\ Doing 7-torsion...
d = 21/11; c = d^2 - d; b = d * c;
do_tate(b, c, 7);
t = ffgen(2^3, 't);
d = t; c = d^2 - d; b = d * c;
do_tate(b, c, 7, 2, 3);

\\ We do these two over a finite field to (1) avoid stack overflow
\\ and (2) to make them run a bit faster.

\\ Doing 8-torsion...
d = 11/7; b = (2 * d - 1) * (d - 1); c = b/d;
do_tate(b, c, 8);
do_tate(b, c, 8, 1009);
t = ffgen(2^3, 't);
d = t; b = (2 * d - 1) * (d - 1); c = b/d;
do_tate(b, c, 8, 2, 3);

\\ Doing 9-torsion...
f = 1/7; d = f * (f - 1) + 1; c = f * (d - 1); b = c * d;
do_tate(b, c, 9);
do_tate(b, c, 9, 61, 2);
t = ffgen(3^3, 't);
f = t; d = f * (f - 1) + 1; c = f * (d - 1); b = c * d;
do_tate(b, c, 9, 3, 3);

\\ Francois Brunault's example
E = ellinit([0, -1, 1, 0, 0]);
z = Mod('t, polcyclo(11, 't));
a = z + 1/z;
xP = a*(a - 1)*(a + 2);
\\ [25]P = 0 on E.
P = [xP, a*xP];
F = ellisogeny(E, P, 0, 'x, 'y);
G = ellisogeny(E, kernel_poly_from_generator(E, P, 25, 'x), 0, 'x, 'y);
if (F[1] != G[1], error("Different curves obtained for same kernel"));
if (F[2] != G[2], error("Different isogenies obtained for same kernel"));
isog_satisfies_eqns(E, F[1], F[2]);

ellisogenyapply(x,x)
ellisogenyapply([f,g,h],1)

E = ellinit([-11/16,-445/32]);
[e2,iso2] = ellisogeny(E,[5/2,0]);
[e4,iso4] = ellisogeny(E,[27/4,17]);
E2 = ellinit(e2);
[e4p,iso2p] = ellisogeny(E2,[11, 0]);
[e4,iso4] == [e4p,ellisogenyapply(iso2p,iso2)]

E=ellinit([1,3]); ellisogeny(E,x-Mod(y,elldivpol(E,2,y)),1)
a='a;
nf=nfinit(a^6-3*a^5+5*a^4-5*a^3+5*a^2-3*a+1);
j=-1904875*a^4+3809750*a^3-4893875*a^2+2989000*a-4426500;
E=ellinit([j],nf);
elliscm(E)
P=x^11+(26363645*a^4-52727290*a^3+67743445*a^2-41379800*a+61284300)*x^10+(425198844352975*a^4-850397688705950*a^3+1092693121108050*a^2-667494276755075*a+988467471418350)*x^9+(2830014116614781625375*a^4-5660028233229563250750*a^3+7272684300788441737125*a^2-4442670184173660111750*a+6578984635750305846375)*x^8+(2263344741088871402400322500*a^4-4526689482177742804800645000*a^3+5816434508071287220399991250*a^2-3553089766982415817999668750*a+5261638163046277899866595000)*x^7+(-84080972281496363098317260784300000*a^4+168161944562992726196634521568600000*a^3-216074670275279855810432355668493750*a^2+131993697993783492712115094884193750*a-195464546125392318289224515971068750)*x^6+(-568351523564746435935291686513186881218750*a^4+1136703047129492871870583373026373762437500*a^3-1460572644706814908984934185280818378875000*a^2+892221121142068473049642498767631497656250*a-1321256992858376468492701626215702401218750)*x^5+(-1707748682214702675736769935984296041081437500000*a^4+3415497364429405351473539871968592082162875000000*a^3-4388641370454173516119348631088285566122972500000*a^2+2680892688239470840382578695103989525041535000000*a-3970034028005570543013063361570151787119815781250)*x^4+(-2363047006543053545682121744696305732886075654271484375*a^4+4726094013086107091364243489392611465772151308542968750*a^3-6072653406936679403703724135909026277503650983280859375*a^2+3709606400393625858021602391212720544617575329009375000*a-5493417809924079435269981996989646540506909179745312500)*x^3+(-280135469328858733032688977354161465279177706559772294921875*a^4+560270938657717466065377954708322930558355413119544589843750*a^3-719903416018950551927048293957869843442972282753550347656250*a^2+439767946690091818894359316603708378163794576193778052734375*a-651235956009982677846293379956586209128269240008555027343750)*x^2+(2968159866535837774480545362636433639107381460850411041514326171875*a^4-5936319733071675548961090725272867278214762921700822083028652343750*a^3+7627696815147200290307277202459139197040556807423060964253681640625*a^2-4659536948611362515826731839822705557933175346572649922739355468750*a+6900134541709030790125148064283533506425208531936964653523935546875)*x+(55842031714753553320268766494358196423992333725337785504940816559863281250/23*a^4-111684063429507106640537532988716392847984667450675571009881633119726562500/23*a^3+143505103031764790871127707693587588851616311527794721536356020007666015625/23*a^2-87663071317011237550858941199229392427623977802456936031415203447802734375/23*a+129816973896318202576954742292189182535432534636464463503237824043554687500/23);
lift(ellisogeny(E,P,1))
