#@ CLASS: E_point

`Class/Declare`("E_point",
 "An instance of this class  represents a point on the embedded surface EX(a)",

 ["Extends","domain_point"],

 ["StaticField","tolerance"::float = 10.^(-98)],
 ["StaticField","max_iterations"::integer = 100],

 ["StaticField","a"::scalar,"This is the value of $a$, where we are considering $EX(a)$."],
 ["StaticField","alpha0"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha1"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha2"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha3"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha4"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha5"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha6"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha7"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha8"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha9"::scalar, "This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha10"::scalar,"This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha11"::scalar,"This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha12"::scalar,"This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha13"::scalar,"This is computed from a by the @set_a()@ method"],
 ["StaticField","alpha14"::scalar,"This is computed from a by the @set_a()@ method"],

 # This should be a static method, but we have not implemented those.
 ["Method","set_a"::void,"",
  proc(this,a_)
   local a;
   a := `if`(nargs > 1, a_, a_E0);
   this["a"] := a;
   this["alpha0" ] := 1 + a^2;
   this["alpha1" ] := 1 - a^2;
   this["alpha2" ] := this["alpha0"]^2;
   this["alpha3" ] := this["alpha1"]^2;
   this["alpha4" ] := sqrt(2*a^2/this["alpha0"]);
   this["alpha5" ] := sqrt(2*(1-a^2));
   this["alpha6" ] := this["alpha0"]*this["alpha5"];
   this["alpha7" ] := this["alpha1"]+this["alpha6"];
   this["alpha8" ] := 1/2 + 7*a^2/2 + this["alpha6"];
   this["alpha9" ] := 2*a*this["alpha5"];
   this["alpha10"] := 4*(1/a^3-5/a);
   this["alpha11"] := 8-2/a^2;
   this["alpha12"] := 4+1/a^2;
   this["alpha13"] := 16-20/a^2+4/a^4;
   this["alpha14"] := (1-1/a^2)^2;
  end
 ],

 ["Field","x"::[scalar,scalar,scalar,scalar],"Coordinates of the point"],
 ["Field","u"::[scalar,scalar,scalar,scalar],"A unit vector orthogonal to $x$, $n$ and $v$"],
 ["Field","v"::[scalar,scalar,scalar,scalar],"A unit vector orthogonal to $x$, $n$ and $u$"],
 ["Field","n"::[scalar,scalar,scalar,scalar],"Normalised gradient of $g$"],
 ["Field","z"::[scalar,scalar],"Basic G-invariant functions of $x$"],
 ["Field","ndg"::scalar,"Norm of the gradient of $g$"],
 ["Field","sndg"::scalar,"Square norm of the gradient of $g$"],
 ["Field","indg"::scalar,"Inverse norm of the gradient of $g$"],
 ["Field","curvature"::scalar,"Gaussian curvature of the surface"],

 ["Method","fix"::void,"",
  proc(this)
   this["fix_x"];
   this["fix_n"];
   this["fix_uv"];
   this["z"] := simp(z_proj0(this["x"]));
   this["curvature"] := simp(curvature0(this["x"]));
   NULL;
  end
 ],

 ["Method","fix_x"::void,
  "This method adjusts the coordinates of a point to ensure that it lies on the surface $EX(a)$ and in the fundamental domain.  Moreover, if the constraint field is set to a value indicating that the point should lie on one of the curves $C_0$, $C_1$, $C_3$ or $C_5$, then the coordinates are adjusted if necessary to ensure that that holds.",
  proc(this)
   local iterations_left,gx,r,err,pp,sndg,x,x1,constraint; 

   x := this["x"];
   x := simp(x);
   this["x"] := x;

   constraint := this["constraint"];

   # If x lies exactly on EX and any boundary constraint is exactly
   # satisfied then we do nothing (and in particular, we do not convert
   # any coordinates to floating point numbers).

   if not(hastype(x,float)) and
      simplify(g0(x)) = 0 and
      simplify(rho(x) - 1) = 0 then
    if constraint = CONSTRAINT_FIXED then
     return;
    elif constraint = CONSTRAINT_C0 and x[3] = 0 and x[4] = 0 then
     return;
    elif constraint = CONSTRAINT_C1 and simplify(x[1] - x[2]) = 0 and x[4] = 0 then
     return;
    elif constraint = CONSTRAINT_C3 and x[1] = 0 then
     return;
    elif constraint = CONSTRAINT_C5 and x[2] = 0 then
     return;
    fi;
   fi;

   # If we reach this point, then we are going to need to do numerical 
   # adjustments, so we convert everything to floating point.
   x := evalf(x);
   this["x"] := x;

   if (constraint = CONSTRAINT_V0) then
    this["set_v0",true];
   elif (constraint = CONSTRAINT_V3) then
    this["set_v3",true];
   elif (constraint = CONSTRAINT_V6) then
    this["set_v6",true];
   elif (constraint = CONSTRAINT_V11) then
    this["set_v11",true];
   elif (constraint >= CONSTRAINT_FIXED) then
    # This should not occur
   elif (constraint = CONSTRAINT_C0) then
     # The general method would be inefficient for points on C0
     x := [max(x[1],0),max(x[2],0),0,0];
     r := sqrt(x[1]*x[1] + x[2]*x[2]);
     this["x"] := x /~ r;
   elif (constraint = CONSTRAINT_C1) then
     # The general method would be inefficient for points on C1
     x := [(x[1]+x[2])/2,(x[1]+x[2])/2,x[3],0];
     r := sqrt(2*x[1]^2 + x[3]^2);
     this["x"] := x /~ r;
   else 
    # We now do a Newton-Raphson procedure to find a nearby point on
    # the surface EX.  The method for points on C3 and C5 is essentially
    # the same as for interior points, except for a couple of extra 
    # lines where we impose the boundary conditions.

    iterations_left := this["max_iterations"];

    x := this["x"];
    r := sqrt(rho(x));
    x := x /~ r;
    gx := g0(x);
    this["x"] := x;
    this["fix_n"];
    sndg := this["sndg"];
    err := 1.;

    while((err > this["tolerance"]) and (iterations_left > 0)) do 
     pp := evalf(gx/sqrt(sndg));
     if (pp >  0.1) then pp :=  0.1; fi;
     if (pp < -0.1) then pp := -0.1; fi;

     this["x"] := this["x"] -~ pp * this["n"];

     if (constraint = CONSTRAINT_C3) then
      this["x"] := map(abs,this["x"] *~ [0,1,1,-1]) *~ [0,1,1,-1];
     fi;

     if (constraint = CONSTRAINT_C5) then
      this["x"] := map(abs,this["x"] *~ [1,0,1,-1]) *~ [1,0,1,-1];
     fi;

     r := sqrt(rho(this["x"]));
     this["x"] := this["x"] /~ r;

     this["fix_n"];
     gx := evalf(g0(this["x"]));
     sndg := this["sndg"];

     err := abs(gx);

     iterations_left := iterations_left - 1;
    od;
   fi;
   NULL;
  end
 ],

 ["Method","fix_n"::void,
  "This method sets @this[\"n\"]@ to the normalisation of the gradient of $g$ at the point @this[\"x\"]@.  It uses the @simp()@ function, which simplifies symbolically unless we have any floating point subexpressions, in which case it evaluates numerically.",
  proc(this)
   local i,n,sndg,ndg,x;

   x := this["x"];

   n := simp(dg0(x));
   sndg := simp(n[1]*n[1] + n[2]*n[2] + n[3]*n[3] + n[4]*n[4]);
   ndg := simp(sqrt(sndg));
   n := simp(n /~ ndg);

   this["n"] := n;
   this["sndg"] := sndg;
   this["ndg"]  := ndg;
   this["indg"] := simp(1/ndg);
   NULL;
  end
 ],

 ["Method","fix_uv"::void,
  "This method sets @this[\"u\"]@ and @this[\"v\"]@ in such a way that @[this[\"x\"],this[\"n\"],this[\"u\"],this[\"v\"]]@ is an oriented orthonormal basis for $\\mathbb{R}^4$.",
  proc(this)
   local r,dp,uu,vv,t,constraint,x,n,u,v,u1,u2,u3,u4;

   constraint := this["constraint"];
   x := this["x"];
   n := this["n"];
   u := this["u"];
   v := this["v"];

   if (constraint = CONSTRAINT_C0) then
    u := [-x[2],x[1],0,0];
    v := [0,0,-2*a_E0,1 - 2*x[1]*x[1]];
    vv := simplify(sqrt(v[3]*v[3] + v[4]*v[4]));
    v := v /~ vv;
   elif (constraint = CONSTRAINT_C1) then
    u := [x[3]/sqrt(2),x[3]/sqrt(2),-x[1]*sqrt(2),0];
    v := [2 - (1+1/a_E0^2)*x[3]*x[3],-2 + (1+1/a_E0^2)*x[3]*x[3],0,4*x[1]*x[3]/a_E0];
    vv := simplify(sqrt(v[1]*v[1] + v[2]*v[2] + v[3]*v[3] + v[4]*v[4]));
    v := v /~ vv;
   elif (constraint = CONSTRAINT_C3) then
    u2 := -a_E0*x[2]*x[2]*x[4]-x[3]*x[3]*x[3]+(a_E0^3+a_E0)*x[4]*x[3]*x[3]+
	      ((3*a_E0^2+2)*x[4]*x[4]+a_E0^2)*x[3]-2*a_E0^3*x[4];
    u3 := -2*x[2]*x[2]*x[2]*a_E0^2+((1-a_E0^2)*x[3]*x[3]+2*a_E0*x[3]*x[4]-2*a_E0^2*x[4]*x[4])*x[2];
    u4 := x[2]*x[2]*x[2]*a_E0+(-2*a_E0*x[3]*x[3]+(-2*a_E0^2-2)*x[4]*x[3])*x[2];

    u := simplify([0,u2,u3,u4]);
    uu := simplify(sqrt(u[2]*u[2] + u[3]*u[3] + u[4]*u[4]));
    u := simplify(u /~ uu);
    v := [1,0,0,0];
   elif (constraint = CONSTRAINT_C5) then
    u1 := -x[3]*x[3]*x[3]-(a_E0^3+2*a_E0)*x[4]*x[3]*x[3]+((3*a_E0^2+2)*x[4]*x[4]+a_E0^2)*x[3]-
	   a_E0*x[4]*x[4]*x[4]+(2*a_E0^3+a_E0)*x[4];
    u3 := ((1+a_E0^2)*x[3]*x[3]-2*a_E0*x[3]*x[4]-2*a_E0^2)*x[1];
    u4 := (3*a_E0*x[3]*x[3]-2*(1+a_E0^2)*x[4]*x[3]+a_E0*x[4]*x[4]-a_E0)*x[1];

    u := simplify([u1,0,u3,u4]);
    uu := simplify(sqrt(u[1]*u[1] + u[3]*u[3] + u[4]*u[4]));
    u := simplify(u /~ uu);
    v := [0,-1,0,0];
   else
    u := [1,1,1,1];
    dp := add(x[t]*u[t],t=1..4);
    u := simplify(u -~ dp *~ x);
    dp := add(n[t]*u[t],t=1..4);
    u := simplify(u -~ dp *~ n);
    u := simplify(u /~ sqrt(add(u[t]^2,t=1..4)));
    v := simplify([
      -x[2]*n[3]*u[4]+x[2]*u[3]*n[4]-n[2]*u[3]*x[4]+n[2]*x[3]*u[4]-u[2]*x[3]*n[4]+u[2]*n[3]*x[4],
       x[1]*n[3]*u[4]-x[1]*u[3]*n[4]-n[1]*x[3]*u[4]+n[1]*u[3]*x[4]+u[1]*x[3]*n[4]-u[1]*n[3]*x[4],
      -x[1]*n[2]*u[4]+x[1]*u[2]*n[4]+n[1]*x[2]*u[4]-n[1]*u[2]*x[4]-u[1]*x[2]*n[4]+u[1]*n[2]*x[4],
       x[1]*n[2]*u[3]-x[1]*u[2]*n[3]-n[1]*x[2]*u[3]+n[1]*u[2]*x[3]+u[1]*x[2]*n[3]-u[1]*n[2]*x[3]
      ]);
    v := simplify(v /~ sqrt(add(v[t]^2,t=1..4)));
   fi;

   this["u"] := simp(u);
   this["v"] := simp(v);
   NULL;
  end
 ],

 ["Method","set_C0"::void,"This method sets the current point to lie on the curve $C_0$.  As the parameter $t$ runs from $0$ to $1$, the point covers the section of $C_0$ contained in the fundamental domain $F_{16}$ starting with $v_6$ at $t=0$, and moving to $v_3$ at $t=1$.",
  proc(this,t::scalar)
   local theta;

   this["constraint"] := CONSTRAINT_C0;
   theta := simp(Pi/4 * (1 + t));
   this["x"] := simp([cos(theta),sin(theta),0,0]);
   this["fix"];
   NULL;
  end
 ],

 ["Method","set_C1"::void,"This method sets the current point to lie on the curve $C_1$.  As the parameter $t$ runs from $0$ to $1$, the point covers the section of $C_1$ contained in the fundamental domain $F_{16}$ starting with $v_0$ at $t=0$, and moving to $v_6$ at $t=1$.",
  proc(this,t::scalar)
   local theta;

   this["constraint"] := CONSTRAINT_C1;
   theta := simp(Pi/2*t);
   this["x"] := simp([sin(theta)/sqrt(2),sin(theta)/sqrt(2),cos(theta),0]);
   this["fix"];
   NULL;
  end
 ],

 ["Method","set_C3"::void,"This method sets the current point to lie on the curve $C_3$.  As the parameter $t$ runs from $0$ to $1$, the point covers the section of $C_3$ contained in the fundamental domain $F_{16}$ starting with $v_{11}$ at $t=0$, and moving to $v_3$ at $t=1$.",
  proc(this,t::scalar)
   local theta,C,S,u0,u1,u2,x2,x3,x4;

   this["constraint"] := CONSTRAINT_C3;
   theta := simp(Pi/2*t);
   C := simp(cos(theta));
   S := simp(sin(theta));

   u0 := this["alpha2"] - 2*this["a"]^2*this["alpha0"] * C^2 + this["alpha3"] * C^4;
   u1 := this["alpha0"] * S^2 + sqrt(u0);
   u2 := this["alpha1"] + 2*this["a"]^2*S^2;
   x2 := S * sqrt(2*u2/u1);
   x3 := this["alpha4"] * C;
   x4 := -x3*u2/(this["a"]*u1);

   this["x"] := simp([0,x2,x3,x4]);

   this["fix"];
   NULL;
  end
 ],

 ["Method","set_C5"::void,"This method sets the current point to lie on the curve $C_5$.  As the parameter $t$ runs from $0$ to $1$, the point covers the section of $C_5$ contained in the fundamental domain $F_{16}$ starting with $v_0$ at $t=0$, and moving to $v_{11}$ at $t=1$.",
  proc(this,t::scalar)
   local theta,C,S,u0,u1,u2,x1,x3,x4;

   this["constraint"] := CONSTRAINT_C5;
   theta := simp(Pi*t);
   C := simp(cos(theta));
   S := simp(sin(theta));

   u0 := this["alpha1"]/2*C^2 - this["alpha7"]*C + this["alpha8"];
   u1 := this["alpha9"]*(1-C) + 4*this["a"];
   u2 := this["alpha1"]*this["alpha5"]*(3-C);
   x1 := -1/2*S*sqrt(u2/u0);
   x3 := sqrt(this["a"]*u1/u0);
   x4 := this["alpha5"]*(C-1)*x3/(4*this["a"]);

   this["x"] := simp([x1,0,x3,x4]);

   this["fix"];
   NULL;
  end
 ],

 ["Method","set_random"::void,"This sets the current point to a randomly generated point in $F_{16}$",
  proc(this) 
   this["set",CONSTRAINT_FREE,retract_F16_E(random_X_point())];
  end
 ],

 NULL
);

######################################################################

#@ CLASS: E_sample_point

`Class/Declare`("E_sample_point",
 "An instance of this class represents a point in a triangular face of a domain of type @E@.  In addition to the usual data for a point in such a domain, it has some additional fields that only make sense relative to the containing face.",

 ["Extends","E_point"],

 ["Field","barycentric_coords"::list(scalar),
  "The (generalised) barycentric coordinates of the point, relative to the containing face.  This is a list of three nonnegative scalars, whose sum is one."
 ],

 ["Field","ibj"::scalar = 0,
  "This field is the Jacobian of the inverse of the barycentric coordinate map.  (TODO: clarify the normalisation conditions.)"
 ],

 ["Field","quadrature_weight"::scalar,
  "This will be set in such a way that the integral (with respect to metric area) of a function on the face can be approximated by the sum of the values at the sample points multiplied by the corresponding quadrature weights"
 ]
);

######################################################################

#@ CLASS: E_edge

`Class/Declare`(
 "E_edge",
 "",
 ["Extends","domain_edge"],

 ["Field","hyperbolic_length"::scalar],

 ["Constructor","",
  proc(this,P0::E_point,P1::E_point)
   this["end"] := table();
   this["set",P0,P1];
  end
 ]
);

#######################################################################

#@ CLASS: E_face

`Class/Declare`(
 "E_face",
 "",
 ["Extends","domain_face"],
 ["Field","samples"::table,"This is a table indexed by some points in the 2-simplex $\\Delta_2$.  The entries are instances of the class @E_sample_point@.  Typically we will fix a triangle quadrature rule @Q@, with points $t_i$ say.  The @samples@ table will have indices $t_i$, and the corresponding entry will represent a point $u_i\\in EX^*$ with barycentric coordinates $t_i$ with respect to this face.  This is set up by the @create_samples@ method."],
 ["Field","sample_list"::list,"This contains the same @E_sample_point@ objects as the @samples@ table, but organised as a list.  This is set up by the @create_samples@ method."],
 ["Field","total_area"::scalar = 0,"The area of this face, with respect to the metric on $EX^*$ inherited from the standard metric on $S^3$.  This is calculated by the @create_samples@ method."],
 ["Field","total_curvature"::scalar = 0,"The integral of the Gaussian curvature function over this face.  This is calculated by the @create_samples@ method."],
 ["Field","barycentric_table","obsolete"],
 
 ["Constructor","",
  proc(this,S0::E_edge,S1::E_edge,S2::E_edge)
   local C;

   this["corner"] := table();
   this["side"] := table();
   this["samples"] := table();
   this["set",S0,S1,S2];
   C := eval(this["corner"]);
  end
 ],

 ["Method","create_samples"::void,
  "This method sets up the fields @samples@, @samples_list@, @total_area@ and @total_curvature@, as described above.",
  proc(this,Q::triangle_quadrature_rule)
   local A,F,tt,ttt,x,xt,P,i,j,k,V,L;

   this["samples"] := table():

   A := [this["corner"][0]["x"],
         this["corner"][1]["x"],
	 this["corner"][2]["x"]];

   L := [];
   this["total_area"] := 0;
   this["total_curvature"] := 0;

   F := barycentric_inverse_F(A);

   for i from 1 to Q["num_points"] do
    tt := Q["points"][i];
    P := `new/E_sample_point`();
    P["set",CONSTRAINT_FREE,barycentric_inverse(A,tt,F)];
    P["ibj"] := simp(1/barycentric_jacobian(A,P["x"]));
    P["quadrature_weight"] := simp(P["ibj"] * Q["weights"][i]); 
    this["samples"][op(tt)] := eval(P);
    L := [op(L),eval(P)];
    this["total_area"] := this["total_area"] + P["quadrature_weight"];
    this["total_curvature"] := this["total_curvature"] + 
      simp(P["quadrature_weight"] * P["curvature"]);
   od;

   this["sample_list"] := L;
   NULL;
  end
 ],

 ["Method","check","This checks the correctness of the sample points.  It returns an offset error (which measures the failure of the points to lie exactly on $EX^*$) and a barycentric error (which measures the failure of the barycentric coordinates to be equal to the indices in the @samples@ table).",
  proc(this)
   local a1,a2,a3,e,x0,x1,x2,t1,tt,i,
	 barycentric_err,barycentric_worst,
	 interpolation_err,interpolation_worst,
	 offset_err,offset_worst,
	 F16_err,F16_worst;

   a1 := this["corner"][0]["x"];
   a2 := this["corner"][1]["x"];
   a3 := this["corner"][2]["x"];

   barycentric_worst := NULL;
   offset_worst := NULL;
   F16_worst := NULL;

   barycentric_err := 0;
   offset_err := 0;
   F16_err := 0;
   
   for tt in indices(this["samples"]) do
    x0 := this["samples"][op(tt)]["x"];
    e := max(abs(rho(x0)-1),abs(g_01(x0)));
    if (e > offset_err) then
     offset_err := e;
     offset_worst := tt;
    fi;

    e := abs(min(evalf([0,x0[1],x0[2],x0[3],g_10(x0)])));
    if (e > F16_err) then
     F16_err := e;
     F16_worst := tt;
    fi;
    
    t1 := barycentric_coords([a1,a2,a3],x0);
    e := d3f(tt,t1);
    if (e > barycentric_err) then
     barycentric_err := e;
     barycentric_worst := tt;
    fi;
   od:

   return [barycentric_err,barycentric_worst,
           offset_err,offset_worst,
	   F16_err,F16_worst];
  end
 ],

 ["Method","plot_x","",
  proc(this,f,vertical_range := -2..2)
   local S;
   S := eval(this["samples"]);
   display(
    seq(point([op(triangle_proj(t0)),f(S[op(t0)]["x"])],colour=red),
     t0 in [indices(S)]),
    triangle_axes(vertical_range),
    axes=none,scaling=constrained
   );
  end
 ],

 ["Method","plot_z","",
  proc(this,f,z_range := -2..2)
   this["plot_x",(u) -> f(z_proj1(u))];
  end
 ],
 
 ["Method","plot_x_ibj","",
  proc(this,f,vertical_range := -2..2)
   local S;
   S := eval(this["samples"]);
   display(
    seq(point([op(triangle_proj(t0)),f(S[op(t0)]["x"]) * S[op(t0)]["ibj"]],colour=red),
     t0 in [indices(S)]),
    triangle_axes(vertical_range),
    axes=none,scaling=constrained
   );
  end
 ],

 ["Method","plot_z_ibj","",
  proc(this,f,z_range := -2..2)
   this["plot_x",(u) -> f(z_proj1(u))];
  end
 ]
);

######################################################################

#@ CLASS: E_grid

`Class/Declare`(
 "E_grid",
 "",
 ["Extends","grid"],
 ["Field","triangle_quadrature_rule"],
 ["Field","int_table"::table,"This is a table indexed by triples $(i,j,k)$; the values are the integrals over $F_{16}$ of $z_1^iz_2^j|n|^k$"],

 ["Constructor",
  "",
  proc(this)
   this["points"] := table();
   this["edges"] := table();
   this["faces"] := table();
   this["edges_by_ends"] := table();
   this["faces_by_corners"] := table();
   this["int_table"] := table();
   this["domain"] := E_domain;
  end
 ],

 ["Method","create_samples"::void,"This invokes the @create_samples@ method for each face.",
  proc(this)
   local i,n,F;
   n := this["num_faces"];
   for i from 0 to n-1 do
    userinfo(6,genus2,sprintf("Creating samples for face %d/%d",i,n));
    F := eval(this["faces"][i]);
    F["create_samples",this["triangle_quadrature_rule"]];
   od;
   NULL;
  end
 ],

 ["Method","int_x",
  "The argument $f$ is assumed to be a polynomial in $x_1,\\dotsc,x_4$.  The method returns an approximation to the integral over $EX(a)$ of $f$, or of $f |n|^k$ if $k$ is given as an additional argument.  Here $n$ is the gradient of $g$.",
  proc(this,f,k_)
   local f1;
   f1 := NF_z(expand(add(act_A[T](f),T in G16)));
   return this["int_z",f1,args[2..-1]];
  end
 ],

 ["Method","int_z",
  "The argument $f$ is assumed to be an expression in $z_1$ and $z_2$.  The method returns an approximation to the integral over F16 of $f$, or of $f |n|^k$ if $k$ is given as an additional argument.  Note that this is 1/16 times the integral over $EX(a)$.",
  proc(this,f,k_)
   local J,i,F,tt,P,f0,k;

   J := 0;
   k := `if`(nargs > 2,k_,0);

   for i in indices(this["faces"]) do
    F := eval(this["faces"][op(i)]);
    for tt in indices(F["samples"]) do
     P := eval(F["samples"][op(tt)]);
     f0 := subs({z[1]=P["z"][1],z[2]=P["z"][2]},f);
     J := J + f0 * P["quadrature_weight"] * P["ndg"]^k;
    od;
   od;

   return(J);
  end
 ],

 ["Method","set_int_table","This sets one value in @int_table@",
  proc(this,i,j,k_)
   local k;
   k := `if`(nargs > 3, k_, 0);
   this["int_table"][i,j,k] := this["int_z",z[1]^i*z[2]^j,k];
  end
 ],

 ["Method","int_z_by_table","This assumes that $f$ is a polynomial in $z_1$ and $z_2$.  It calculates the integral of $f$ over $F_{16}$ by looking up the integrals of the individual monomials in @int_table@.  If any of the required integrals are missing from @int_table@, then they will be calculated and saved there.",
  proc(this,f,k_)
   local f0,k,n1,n2,i1,i2,b,c,J;
   f0 := expand(f);
   k := `if`(nargs > 2,k_,0);

   n1 := degree(f0,z[1]);
   n2 := degree(f0,z[2]);
   J := 0;

   for i1 from 0 to n1 do
    for i2 from 0 to n2 do
     c := coeff(coeff(f0,z[1],i1),z[2],i2);
     if c <> 0 then
      b := this["int_table"][i1,i2,k];
      if not(type(b,numeric)) then
       this["set_int_table",i1,i2,k];
       b := this["int_table"][i1,i2,k];
      fi;
      J := J + b * c;
     fi;
    od;
   od;
   return(J);
  end
 ],

 ["Method","set_max_deg"::void,"Calculate and save integrals for all monomials in $z_1$ and $z_2$ of degree at most $d$",
  proc(this,d::posint)
   local i,j,m,u;

   for m from 0 to d do
    for i from 0 to m do
     j := m - i;
     u := z[1]^i * z[2]^j;
     userinfo(7,genus2,sprintf("Integrating %A",u));
     this["int_z_by_table",u];
    od;
   od:
   NULL;
  end
 ],

 ["Method","total_area","This returns the total area of $F_{16}$",
  proc(this)
   add(this["faces"][i]["total_area"],i=0..this["num_faces"]-1);
  end
 ],

 ["Method","total_curvature","This returns the integral of the curvature over $F_{16}$",
  proc(this)
   add(this["faces"][i]["total_curvature"],i=0..this["num_faces"]-1);
  end
 ],

 ["Method","curvature_error","If the integration rule is accurate, then the result of this method should be zero, by the Gauss-Bonet Theorem.",
  proc(this)
   evalf(Pi/4 + this["total_curvature"]);
  end
 ],

 ["Method","stokes_error","Here @ff@ should be a list $(f_1,f_2)$ of two expressions in $z_1$ and $z_2$.  The method returns the approximated integral of the exterior derivative of $f_1\\alpha_1+f_2\\alpha_2$, where the forms $\\alpha_i$ are defined in @embedded/roothalf/forms.mpl@.  If the integration rule is accurate, then the result should be zero, by Stokes's Theorem.",
  proc(this,ff)
   if `class/E_point`["StaticFieldValue"]["a"] <> 1/sqrt(2) then
    return(FAIL);
   fi;
   
   this["int_z",stokes_alpha(ff)];
  end
 ]
);

######################################################################

#@ E_domain

E_domain := `new/domain`():
E_domain["set_point_class","E_point"]:
E_domain["set_edge_class" ,"E_edge" ]:
E_domain["set_face_class" ,"E_face" ]:
E_domain["set_grid_class" ,"E_grid" ]:

if (type(a_E0,realcons)) then
 `new/E_point`()["set_a",a_E0];
fi:

######################################################################