#!/bin/bash maple
# Use `maple -q expand6.mpl -D n=100` to run

e := a0 + a1:
f := 0:

for i from 2 to (n - 1)
do
    f := f + cat(a, convert(i, string)):
    e := e + cat(a, convert(i, string)):
end do:

f := -f:
e := e ^ 2:

st := time[real]():
e := expand(e):
e := subs(a0 = f, e):
e := expand(e):
1000*(time[real]()-st);

print(e);

done


with(Groebner):

################ proc PolylistToNormal
# input:
# Polylist: a nonempty list of nonzero polynomials which are different from each other (list)
# VarList: a list of variables which appear in Polylist (list)
# output: If Polylist has finite solutions, then the output is a strong triangular decomposition (STD) of Polylist.
#         (a row vector of some row vectors)
#         Else FAIL.
PolylistToNormal := proc(PolyList, VarList)
local i, k, ans, TriList, j;
for i from 1 to nops(PolyList) do
    if type(PolyList[i], constant)=true then
       return Vector[row]();
    end if;
end do;
TriList := IsTri(PolyList, VarList);
if TriList[1]=true then
    if IniConstant(PolyList, TriList[2])=0 then
        if nops(PolyList)=nops(VarList) then
            ans := Vector[row](nops(VarList));
            for i from 1 to nops(TriList[3]) do
                k := TriList[3][i];
                ans[-i] := PolyList[k];
            end do;
            for i from 2 to nops(VarList) do
                ans[i] := NormalForm(ans[i], [seq(ans[j], j=1..i-1)], plex(op(VarList)));
            end do;
            return Vector[row](1, fill = ans);
        else
            error "FAIL1: This is not a zero-dimensional system.";
        end if;
    else
        return SubPolylistToNormal(PolyList, VarList, 1, 1);
    end if;
else
    return SubPolylistToNormal(PolyList, VarList, 2, 1);
end if;
end proc:

################ proc SubPolylistToNormal
# GBmethod: 0: fgb+convert
#           1: buchberger
#           2: the first time 0, else 1 (def)
# SATGBmethod: 0: fgb+convert
#              1: buchberger
SubPolylistToNormal := proc(PolyList, VarList, GBmethod := 2, SATGBmethod := 1)
local ComputedSet, GBmethod1, SplitSet, P, GB_Grad, GB_Lex, wCharList, AbnormalNum, IniNum, GB, AddF, i, GB_mainvar, lindex, lnum;
ComputedSet := Vector[row]();   # ComputedSet is a row vector of some row vectors
SplitSet := Vector[row]();      # SplitSet is a row vector of some lists
SplitSet(1) := PolyList;
GBmethod1 := GBmethod;
lindex := 1;
lnum := 1;
while lindex<=lnum do
    P := SplitSet[lindex];
    lindex := lindex+1;
    if GBmethod1=1 then
        GB_Lex := Basis(P, plex(op(VarList)), method = Buchberger);
    else
        GB_Grad := Basis(P, tdeg(op(VarList)), method = fgb);
        GB_Lex  := Basis(GB_Grad, plex(op(VarList)), method = convert);
        if GBmethod1=2 then
            GBmethod1 := 1;
        end if;
    end if;
    if GB_Lex <> [1] then
       GB_mainvar := [seq(MainVar(GB_Lex[i], VarList), i=1..nops(GB_Lex))];
       if lindex=2 then
           if not(IsZeroDim(GB_Lex, VarList)) then
              error "FAIL2: This is not a zero-dimensional system.";
           end if;
       end if;
       wCharList := wChar(GB_Lex, VarList, GB_mainvar); # wCharList is a row vector
       AbnormalNum := IsNormal(wCharList, VarList);
       if AbnormalNum = 0 then
            ComputedSet(numelems(ComputedSet)+1) := wCharList;
       else
            IniNum := SquareFree(primpart(lcoeff(wCharList[AbnormalNum], VarList[-AbnormalNum])));
            GB := SaturIdeal(IniNum, [seq(wCharList[i], i=1..AbnormalNum-1)], VarList, SATGBmethod); # GB is a row vector
            AddF := Vector[row]();
            for i from 1 to numelems(GB) do
                if NormalForm(GB[i], [seq(wCharList[i], i=1..AbnormalNum-1)], plex(op(VarList)))<>0 then
                    AddF(numelems(AddF)+1) := GB[i];
                end if;
            end do;
            SplitSet(++lnum):= [op(GB_Lex), seq(AddF[i], i=1..numelems(AddF))];
            SplitSet(++lnum):= [op(GB_Lex), IniNum];
       end if;
    end if;
end do;
return ComputedSet;
end proc:

################ proc NormalToSquarefree
# input:
# NormalChain: a strong chain (a row vector)
# VarList: a list of variables which appear in Polylist (list)
# output:
# ans: a strong square-free triangular decomposition (SSFTD) of NormalChain
#         (a row vector of some row vectors)
NormalToSquarefree := proc(NormalChain, VarList, SATGBmethod:=1)
local NormalSet, ans, P, AbNum, temp, tempSet, f, GB, AddF, i, r1, r2, lindex, lnum;
NormalSet := Vector[row]();   # NormalSet is a vector of some row vectors
NormalSet(1) := NormalChain;
ans := Vector[row]();         # ans is a vector of some row vectors
lindex := 1;
lnum := 1;
while lindex<=lnum do
   P := NormalSet[lindex];           # P is a row vector
   lindex := lindex+1;
   AbNum := IsSquareFree(P, VarList);
   if AbNum=0 then
        ans(numelems(ans)+1) := P;
   elif AbNum=1 then
        temp := factors(P[1]);
        tempSet := [seq(temp[2][i][1], i = 1 .. nops(temp[2]))];
        for i from 1 to nops(tempSet) do
            P(1) := tempSet[i];
            NormalSet(++lnum) := Vector[row](P);
        end do;
    else
        f := SquareFree(diff(P[AbNum], VarList[-AbNum]));
        GB := SaturIdeal(f, [seq(P[i], i=1..AbNum)], VarList, SATGBmethod); # GB is a row vector
        AddF := Vector[row]();
        for i from 1 to numelems(GB) do
            if NormalForm(GB[i], [seq(P[i], i=1..AbNum)], plex(op(VarList)))<>0 then
                AddF(numelems(AddF)+1) := GB[i];
            end if;
        end do;
        r1 := SubPolylistToNormal([seq(P[i], i=1..numelems(P)), f], VarList); # r1 is a row vector of some row vectors
        r2 := SubPolylistToNormal([seq(P[i], i=1..numelems(P)), seq(AddF[i], i=1..numelems(AddF))], VarList);
        # r2 is a row vector of some row vectors
        for i from 1 to numelems(r1) do
            NormalSet(++lnum) := r1[i];
        end do;
        for i from 1 to numelems(r2) do
            NormalSet(++lnum) := r2[i];
        end do;
    end if;
end do;
return ans;
end proc:

################ proc PolysetToSfNormal
# input:
# Polylist: a nonempty list of nonzero polynomials which are different from each other (list)
# VarList: a nonempty list of variables
# output:
# output: If Polylist has finite solutions, then the output is a strong square-free triangular decomposition (SSFTD) of Polylist.
#         (a row vector of some row vectors)
#         Else FAIL.
PolylistToSfNormal := proc(PolySet, VarList, SATGBmethod:=1)
local NormalDec, ans, i, r, j;
NormalDec := PolylistToNormal(PolySet, VarList);   # NormalDec is a row vector of some row vectors
if NormalDec=FAIL then
   return FAIL;
else
   ans := Vector[row]();     # ans is a row vector of some row vectors
   for i from 1 to numelems(NormalDec) do
       r := NormalToSquarefree(NormalDec[i], VarList, SATGBmethod);    # r is a row vector of some row vectors
       for j from 1 to numelems(r) do
           ans(numelems(ans)+1) := r[j];
       end do;
   end do;
   return ans;
end if;
end proc:

################ proc IsTri
IsTri := proc(PolyList, VarList)
local JudgeList, i, k, mainvarList;
JudgeList := [seq(-1, i=1..nops(VarList))];
mainvarList := [seq(MainVar(PolyList[i], VarList), i=1..nops(PolyList))];
for i from 1 to nops(PolyList) do
    member(mainvarList[i], VarList, 'k');
    if JudgeList[k]=-1 then
        JudgeList[k] := i;
    else
        return [false];
    end if;
end do;
return [true, mainvarList, JudgeList];
end proc:

################ proc wChar
# input:
# GroeBasis: the reduced lex Groebner basis w.r.t. VarList[1]>...>VarList[n]
# VarList: the variable list of GroeBasis
# mainvar: the main variable list of GroeBasis such that the main variable of GroeBasis[i] is mainvar[i]
# output:
# wCharList: the W-characteristic list of GroeBasis (row vector)
wChar := proc(GroeBasis, VarList, mainvar)
local k, i, PolySet, wCharList, wCharPrep, j;
PolySet := Vector[row](nops(VarList));
for k from 1 to nops(VarList) do
    PolySet[k] := Vector[row]();
end do;
for i from 1 to nops(GroeBasis) do
    member(mainvar[i], VarList, 'k');
    PolySet[k](numelems(PolySet[k])+1) := GroeBasis[i];
end do;
wCharList := Vector[row](nops(VarList));
for i from 1 to nops(VarList) do
    wCharPrep := PolySet[i][1];
    for j from 2 to numelems(PolySet[i]) do
        if TestOrder(LeadingMonomial(PolySet[i][j], plex(op(VarList))), LeadingMonomial(wCharPrep, plex(op(VarList))), plex(op(VarList))) then
           wCharPrep := PolySet[i][j];
        end if;
    end do;
    wCharList[-i] := wCharPrep;
end do;
return wCharList;
end proc:

################ proc IsNormal
# input:
# TriSet: a zero-dim triangle set T=[T1,...,Tn] such that 0<lv(T1)<...<lv(Tn) (row vector)
# VarList: the list of main variables of TriSet such that the main variable of TriSet[i] is VarList[-i] (list)
# output:
# NormNum: if Triset is normal, then return 0
# else return the smallest integer (>1) such that [T1,...,Tk] is abnormal
IsNormal := proc(TriSet, VarList)
local NormNum, i;
NormNum := 0;
for i from 1 to numelems(TriSet) do    # Note that Triset[1] is a univariate polynomial.
    if type(lcoeff(TriSet[i], VarList[-i]), constant)=false then
       NormNum := i;
       break;
    end if;
end do;
return NormNum;
end proc:

################ proc IsNormalSpe
# input:
# PolyList: a polynomial list which is a out-of-order triangle set (a list)
# mainvarList: lv(PolyList[i]) = mainvarList[i]
# output:
# NormNum: if all initials of PolyList are constants, then return 0; else return 1
IniConstant := proc(PolyList, mainvarList)
local i;
for i from 1 to nops(PolyList) do    # Note that Triset[1] is a univariate polynomial.
    if type(lcoeff(PolyList[i], mainvarList[i]), constant)=false then
       return 1;
    end if;
end do;
return 0;
end proc:

################ proc IsSquareFree
# input:
# TriSet: a triangle set T=[T1,...,Tn] such that 0<lv(T1)<...<lv(Tn) (a row vector)
# (a list)
# VarList: the list of main variables of TriSet such that the main variable of TriSet[i] is VarList[-i]
# output:
# NormNum: if Triset is square-free, then return 0
# else return the smallest integer such that [T1,...,Tk] is not squarefree
IsSquareFree := proc(TriSet, VarList)
local NormNum, j, i, dis, Tri;
NormNum := 0;
if primpart(discrim(TriSet[1], VarList[-1]))=0 then
   return 1;
else
   for i from 2 to numelems(TriSet) do
        dis[i] := primpart(discrim(TriSet[i], VarList[-i]));
        Tri := [seq(TriSet[j], j=1..i-1)];
        if ResTri(dis[i], Tri, VarList)=0 then
           NormNum := i; break;
        end if;
        if NormNum <> 0 then break; end if;
    end do;
    return NormNum;
end if;
end proc:

################ proc ResTri
# input:
# P : a polynomial
# TriSet: a triangle set T=[T1,...,Tn] such that 0<lv(T1)<...<lv(Tn)   (a list)
# VarList: the list of main variables of TriSet such that the main variable of TriSet[i] is VarList[-i]
# output:
# F: Res(P, TriSet)
ResTri := proc(P, TriSet, VarList)
local F, n,i,j,temp,tempSet1,tempSet2;
F := P;
for n from nops(TriSet) by -1 to 1 do
    F := primpart(resultant(F, TriSet[n], VarList[-n]));
end do;
return F;
end proc:

################ proc SaturIdeal
# input:
# Poly : a polynomial
# PolyList: a list of polynomials (a list)
# SATGBmthod: 0: fgb+convert; 1: buchberger
# output:
# ans: the lex reduced Groebner basis of <op(PolyList)>:Poly^{infinity} (a vector)
SaturIdeal := proc(Poly, PolyList, VarList, SATGBmethod:=0)
local Z, GB_Grad, GB_Lex, i, ans;
ans := Vector[row]();
if SATGBmethod=0 then
    GB_Grad := Basis([op(PolyList), 1-Poly*Z], tdeg(Z, op(VarList)), method = fgb);
    GB_Lex  := Basis(GB_Grad, plex(Z, op(VarList)), method = convert);
else
    GB_Lex  := Basis([op(PolyList), 1-Poly*Z], plex(Z, op(VarList)), method = buchberger);
end if;
for i from 1 to nops(GB_Lex) do
    if has(GB_Lex[i], Z)=false then
       ans(numelems(ans)+1) := GB_Lex[i];
    end if;
end do;
return ans;
end proc:

########## proc Class, proc MainVar, proc WuInitial
# input:
# f: a polynomial
# ord: a list of variables of f such that ord[1]>ord[2]>...
########## proc Class
Class := proc(f::polynom, ord::list)
local i;
option cache;
for i from 1 to nops(ord) do
	if has(f,ord[i]) then
		return i;
	end if;
end do;
return 0;
end proc:

########## proc MainVar
MainVar := proc(f::polynom, ord::list)
local i;
option cache;
for i from 1 to nops(ord) do
	if has(f, ord[i]) then
		return ord[i];
	end if;
end do;
return 0;
end proc:

########## proc WuInitial
WuInitial := proc(f::polynom, ord::list)
option cache;
if Class(f, ord)=0 then
	return 1;
else
	return primpart(lcoeff(f, MainVar(f,ord)));
end if;
end proc:

########## proc SquareFree
SquareFree := proc(poly)
option cache;
local FactorList,i;
FactorList := sqrfree(poly);
return mul(FactorList[2][i][1], i = 1 .. nops(FactorList[2]));
end proc:

########## proc IsZeroDim
IsZeroDim := proc(GBlex, VarList)
local JudgeList,i,IndSet,j;
JudgeList := [seq(0, i=1..nops(VarList))];
for i from 1 to nops(GBlex) do
   IndSet := indets(LeadingMonomial(GBlex[i], plex(op(VarList))));
   if nops(IndSet)=1 then
       for j from 1 to nops(VarList) do
           if IndSet={VarList[j]} then
               JudgeList[j] := 1;
               break;
           end if;
       end do;
   end if;
end do;
for i from 1 to nops(JudgeList) do
   if JudgeList[i]=0 then
       return false;
   end if;
end do;
return true;
end proc:


(*
    Test file for highlight
*)
`&+`:=proc(x::set,y::set)
    return x union y;
end proc:               # keywords
$include "utils.mpl"    # preprocessor
solve(x^2+x-1);         # maple functions
print(`&+`);            # quoted name
print("123");           # string
print('x+y');           # unvaluated expression
type(x,uneval);         # type name
fun:=proc(x::uneval)    # parameter modifier
    option inline;      # proc and module options
    print(procname);    # language variable
    return true;        # language constants
end proc:


# Definitions for Parallel Robot Dynamics Code Generation
# Einleitung
# Erstelle Definitionen für die Maple-Skripte zur Berechnung von Kinematik und Dynamik des parallelen Roboters
#
# Dateiname:
# robot -> Berechnung für allgemeinen Roboter
# para -> Berechnung für eine parallelen Roboter
# definitions -> Definitionen
# Autor
# Tim Job (Studienarbeit bei Moritz Schappler), 2018-12
# Moritz Schappler, moritz.schappler@imes.uni-hannover.de
# (C) Institut für Mechatronische Systeme, Universität Hannover
# Init
interface(warnlevel=0): # Unterdrücke die folgende Warnung.
restart: # Gibt eine Warnung, wenn über Terminal-Maple mit read gestartet wird.
interface(warnlevel=3):
with(LinearAlgebra):
read "../helper/proc_MatlabExport":
read "../helper/proc_convert_t_s":
# Lese Umgebungsvariable für Codegenerierung.
read "../robot_codegen_definitions/robot_env_par":
printf("Generiere Parameter für %s\n",robot_name):
read sprintf("../codeexport/%s/tmp/tree_floatb_definitions", leg_name):
# Link-Index, für den die Jacobi-Matrix aufgestellt wird. Hier wird angenommen, dass der Endeffektor das letzte Segment (=Link) ist. Die Jacobi-Matrix kann hier aber für beliebige Segmente aufgestellt werden. (0=Basis)
LIJAC:=NL-1:
# Ergebnisse der analytischen Jacobi-Matrix (Translatorisch)
px, py, pz := 0, 0, 0:
alphaxs_base, betays_base, gammazs_base := 0, 0, 0:
read sprintf("../codeexport/%s/tmp/jacobia_transl_%d_maple.m", leg_name, LIJAC):
b_transl := b_transl:
# Lese Umgebungsvariable für Codegenerierung.
read "../robot_codegen_definitions/robot_env_par":
# Additional Definitions
# Parameter definieren, wenn nicht vorher schon geschehen
if not assigned(J_SP) then
   J_SP := Matrix(3,3,[XX,XY,XZ,XY,YY,YZ,XZ,YZ,ZZ]):
end if:
if not assigned(J_P_P) then
   J_P_P := Matrix(3,3,[XXFP,XYFP,XZFP,XYFP,YYFP,YZFP,XZFP,YZFP,ZZFP]):
end if:
if not assigned(xE_s) then
   xE_s:=<x_all[1];x_all[2];x_all[3];x_all[4];x_all[5];x_all[6]>:
end if:

# Gravity vector in world frame
if not assigned(g_world) then
  g_world := Matrix(3, 1):
  g_world(1 .. 3, 1) := <g1, g2, g3>:
end if:

# Parallel Robotics Definitions
# Erstelle für Gelenkkoordinaten, -geschwindigkeiten und -beschleunigungen für jedes Bein
J := simplify(b_transl):
vars := indets(J):
counter := 0:
thetaList := convert(vars,list):
dList := convert(d,list):
for i to NQJ do
  var := parse(sprintf("qJ%ds", i)):
  if has(thetaList, var) then
    counter := counter + 1:
  end if:
  if has(dList, var) then
    counter := counter + 1:
  end if:
end do:
NQJ_parallel := counter:
counter := 0:
for i to ColumnDimension(J) do
  if not(Equal(J(..,i),Vector(3,[0,0,0]))) then
    counter := counter + 1:
  end if:
end do:
NQJ_parallel := counter:

qJ_i_s := Matrix(NQJ_parallel,N_LEGS):
qJD_i_s := Matrix(NQJ_parallel,N_LEGS):
qJDD_i_s := Matrix(NQJ_parallel,N_LEGS):
for i from 1 to NQJ_parallel do
  for j from 1 to N_LEGS do
     k := j-1:
     qJ_i_s(i,j):=parse(sprintf("qJ%ds", i+k*NQJ_parallel)):
     qJD_i_s(i,j):=parse(sprintf("qJD%ds", i+k*NQJ_parallel)):
     qJDD_i_s(i,j):=parse(sprintf("qJDD%ds", i+k*NQJ_parallel)):
  end do:
end do:
# Erstelle Vektor der Basisdrehungen jedes Beines
frame_A_i := Matrix(3,N_LEGS):
for i to N_LEGS do
   for j to 3 do
      if has(indets(leg_frame(4..6)),leg_frame(j+3)) then
         frame_A_i(j,i) := leg_frame(j+3)[i]:
      else
         frame_A_i(j,i) := leg_frame(j+3):
      end if:
   end do:
end do:
# Plattformschwerpunkt
r_P_sP := Vector(3,symbol=r_sP):
s_P_sP := Vector(3,symbol=s_sP):
angleConvLeg := leg_frame(7):
angleConv := xE_s(7):
# Erstelle EE-Koordinaten: xE_t, xED_t, xEDD_t -> Variablen mit Zeitabhängigkeit
#                                          xE_s, xED_s, xEDD_s -> Variablen ohne Zeitabhängigkeit
xE_t:=<xE(t);yE(t);zE(t);PsiE(t);ThetaE(t);PhiE(t)>:
xED_t:=diff~(xE_t,t):
xEDD_t:=diff~(xED_t,t):
# Zähle Freiheitsgrade des Roboters und setze nicht benötigte zu null.
xE_s := Matrix(xE_s(1..6,1)):
xED_s := copy(xE_s):
xEDD_s := copy(xE_s):
FHG_trans := 0:FHG_rot := 0:
counter := 6:
for i to 6 do
   if not(xE_s(i) = 0) then
   	 Tmp := xE_s(i):
      xED_s(i) := D||Tmp:
      xEDD_s(i) := DD||Tmp:
   else
   	 counter := counter - 1:
      xED_s(i) := 0:
      xEDD_s(i) := 0:
      xE_t(i) := 0:
      xED_t(i) := 0:
      xEDD_t(i) := 0:
   end if:
end do:
NX := counter:
counter := 0:
for i from 4 to 6 do
	if not(leg_frame(i) = 0) then
		counter := counter + 1:
	end if:
end do:
legAngles := counter:
# Export
# Maple-Export
save g_world, J_P_P, s_P_sP, NX, NQJ_parallel, angleConvLeg, angleConv, r_P_sP, frame_A_i, qJ_i_s, qJD_i_s, qJDD_i_s, xE_t, xED_t, xEDD_t, xE_s, xED_s, xEDD_s, J_SP, sprintf("../codeexport/%s/tmp/para_definitions", robot_name):
varScript := <NX;NQJ_parallel;legAngles>:
MatlabExport(varScript, sprintf("../codeexport/%s/tmp/var_parallel.m", robot_name), 1);


#!/bin/bash maple
# Use `maple -q expand2.mpl -D n=15` to run

e := (x + y + z + w) ^ n:
f := e * (e + w):

st := time[real]():
f := expand(f):
1000*(time[real]() - st);

done


# The full Eilenberg-Zilber operad EZ has EZ(A) equal to the set of
# natural maps N_*(X) \to N_*(X)^{\otimes A} for simplicial sets X.
# By consideration of universal examples, we see that any such map
# sends N_{\leq n}(X) into (N_{\leq n}(X))^{\otimes A} for all n.
# Thus, if we define EZ_{\leq n}(A) to be the set of natural maps
# N_{\leq n}(X) \to (N_{\leq n}(X))^{\otimes A}, we get a quotient
# operad of EZ.  The group EZ(A) is an infinite product of copies of
# the integers and so is not computationally tractable.  We
# therefore work with EZ_{\leq n} instead.

`is_element/eilenberg_zilber` := (n :: nonnegint) -> (A::set) -> proc(x)
 local y,z,i;

 if x = 0 then return true; fi;

 if type(x,`+`) then
  return `and`(op(map(`is_element/eilenberg_zilber`(n)(A),[op(x)])));
 fi;

 if type(x,`*`) then
  y,z := selectremove(type,[op(x)],integer);
  return (nops(z) = 1 and `is_element/eilenberg_zilber`(n)(A)(z[1]));
 fi;

 if not type(x,specfunc(Z)) then return false; fi;

 if nops(x) = 0 or nops(x) > n+1 then return false; fi;

 for i from 1 to nops(x) do
  if not(`is_element/subsets`(A)(op(i,x))) then
   return false;
  fi;
 od;

 if map(op,{op(x)}) <> A then
  return false;
 fi;

 for i from 1 to nops(x) - 1 do
  if op(i,x) intersect op(i+1,x) = {} then return false; fi;
 od;

 return true;
end:

`is_equal/eilenberg_zilber` := (n::nonnegint) -> (A::set) -> (x,y) -> evalb(x = y):

`random_basis_element/eilenberg_zilber` := (n::nonnegint) -> (A) -> proc(m_)
 local m,t,M,a,i;

 if A = {} then
  if nargs = 0 or m_ = 0 then
   return Z({});
  else
   error("A is empty");
  fi;
 fi;

 m := `if`(nargs > 0,min(n,m_),rand(0..n)());

 t := table():
 M := {seq(i,i=0..m)};

 for a in A do t[a] := random_nonempty_subset_of(M); od;

 for i from 0 to m-1 do
  a := random_element_of(A);
  t[a] := t[a] union {i,i+1};
 od:

 return Z(seq(select(a->member(i,t[a]),A),i=0..m));
end:

`random_element/eilenberg_zilber` := (n::nonnegint) -> (A) -> proc(coeff_range_,num_terms_)
 local coeff_range,num_terms;

 coeff_range := -3..3;
 num_terms := 5;
 if nargs > 0 then coeff_range := args[1]; fi;
 if nargs > 1 then num_terms := args[2]; fi;

 add(rand(coeff_range)() * `random_basis_element/eilenberg_zilber`(n)(A)(),i=1..num_terms);
end:

`transpose0/eilenberg_zilber` := (A::set) -> proc(x)
 local m,M,t,a;

 m := nops(x) - 1;
 M := [seq(i,i=0..m)];
 t := table():
 for a in A do
  t[a] := select(i -> member(a,op(i+1,x)),M);
 od;

 return Zt(eval(t));
end;

`transpose/eilenberg_zilber` := (A::set) ->
 apply_linear(`transpose0/eilenberg_zilber`(A));

`detranspose0/eilenberg_zilber` := (A::set) -> proc(x)
 local t,m;

 t := op(1,x);
 m := max(op(map(op,{{0},seq(t[a],a in A)})));
 return Z(seq(select(a -> member(i,t[a]),A),i=0..m));
end:

`detranspose/eilenberg_zilber` := (A::set) ->
 apply_linear(`detranspose0/eilenberg_zilber`(A));

`eta/eilenberg_zilber` := (n::nonnegint) -> (A::set) ->
 `if`(nops(A)=1,add(Z(A $ k),k=1..(n+1)),FAIL);

`source_degree/eilenberg_zilber` := (A::set) -> proc(x)
 return nops(x) - 1;
end:

`target_degrees/eilenberg_zilber` := (A::set) -> proc(x)
 local xt,d,a;

 xt := op(1,`transpose/eilenberg_zilber`(A)(x));
 d := table();
 for a in A do
  d[a] := nops(xt[a]) - 1;
 od:

 return eval(d);
end:

`gamma0/eilenberg_zilber` := (A::set,B::set) -> (p) -> proc(y,x)
 local F,s,d,t,u,a,b;

 F := fibres(A,B)(p);
 s := op(1,`transpose/eilenberg_zilber`(B)(y));
 d := `target_degrees/eilenberg_zilber`(B)(y);

 t := table():
 for b in B do
  if `source_degree/eilenberg_zilber`(F[b])(x[b]) <> d[b] then
   return 0;
  fi;

  t[b] := op(1,`transpose/eilenberg_zilber`(F[b])(x[b]));
 od;

 u := table();
 for a in A do
  b := p[a];
  u[a] := [seq(s[b][j+1], j in t[b][a])];
 od;

 return `detranspose/eilenberg_zilber`(A)(Zt(u));
end:

`gamma/eilenberg_zilber` := (n::nonnegint) -> (A::set,B::set) -> (p) -> proc(y,x)
 local c,y1,F,d0,BL,M,t,z,b,m,mt;

 if y = 0 then
  return 0;
 elif type(y,`+`) then
  return map(t -> `gamma/eilenberg_zilber`(n)(A,B)(p)(t,x),y);
 fi;

 if type(y,`*`) then
  c,y1 := selectremove(type,y,integer);
 else
  c := 1;
  y1 := y;
 fi;

 if not(type(y1,specfunc(Z))) then
  return FAIL;
 fi;

 F := fibres(A,B)(p);

 d0 := `target_degrees/eilenberg_zilber`(B)(y1);

 BL := sort([op(B)]);
 M := [[1]];
 for b in BL do
  t := map(coeff_split,sum_terms(x[b]));
  t := select(u -> `source_degree/eilenberg_zilber`(F[b])(u[2]) = d0[b],t);
  M := [seq(seq([m[1] * u[1],seq(m[i],i=2..nops(m)),u[2]],u in t),m in M)];
 od:

 z := 0;

 for m in M do
  mt := table([seq(BL[i]=m[i+1],i=1..nops(BL))]);
  z := z + c * m[1] * `gamma0/eilenberg_zilber`(A,B)(p)(y1,mt);
 od;

 return z;
end:


#@ Not autoload

# This was originally written for Mathematica, and needs more
# translation.

#**************************************************************
# the rest of this does not appear in Deligne's formulaire.

# The cubical structure

#h[{{x0_,y0_,z0_},
#   {x1_,y1_,z1_},
#   {x2_,y2_,z2_}}] :=
#     Det[{{x0,y0,z0},{x1,y1,z1},{x2,y2,z2}}] z0 z1 z2 /
#     ( Det[{{x0,z0},{x1,z1}}] *
#       Det[{{x1,z1},{x2,z2}}] *
#       Det[{{x2,z2},{x0,z0}}] )

# z in terms of x where y = 1, valid mod x^10

#zx =
#  x^3 - a1 x^4 + a1^2 x^5 + a2 x^5 - a1^3 x^6 - 2 a1 a2 x^6 - a3 x^6 +
#   a1^4 x^7 + 3 a1^2 a2 x^7 + a2^2 x^7 + 3 a1 a3 x^7 + a4 x^7 - a1^5 x^8 -
#   4 a1^3 a2 x^8 - 3 a1 a2^2 x^8 - 6 a1^2 a3 x^8 - 3 a2 a3 x^8 -
#   3 a1 a4 x^8 + a1^6 x^9 + 5 a1^4 a2 x^9 + 6 a1^2 a2^2 x^9 + a2^3 x^9 +
#   10 a1^3 a3 x^9 + 12 a1 a2 a3 x^9 + 2 a3^2 x^9 + 6 a1^2 a4 x^9 +
#   3 a2 a4 x^9 + a6 x^9

# Invariant differential mod x^10, where y = 1

#omx =
#  dx (-1 + a1 x - a1^2 x^2 - a2 x^2 + a1^3 x^3 + 2 a1 a2 x^3 + 2 a3 x^3 -
#     a1^4 x^4 - 3 a1^2 a2 x^4 - a2^2 x^4 - 6 a1 a3 x^4 - 2 a4 x^4 +
#     a1^5 x^5 + 4 a1^3 a2 x^5 + 3 a1 a2^2 x^5 + 12 a1^2 a3 x^5 +
#     6 a2 a3 x^5 + 6 a1 a4 x^5 - a1^6 x^6 - 5 a1^4 a2 x^6 - 6 a1^2 a2^2 x^6 -
#     a2^3 x^6 - 20 a1^3 a3 x^6 - 24 a1 a2 a3 x^6 - 6 a3^2 x^6 -
#     12 a1^2 a4 x^6 - 6 a2 a4 x^6 - 3 a6 x^6)

# The following functions are invertible near [0:1:0], and
# they satisfy uu ww = vv^3.  Note that vv depends only on y
# and z.  Moreover, {uu != 0} \cap {x = 0} = {0} as schemes.

#uu = y^2 + a1 x y - a2 x^2 + a3 y z - a4 x z - a6 z^2
#vv = y^2 + a3 y z - a6 z^2
#ww = a1^2 x^2 y^2 + a2 x^2 y^2 - a1 x y^3 + y^4 + a2 a3 x^2 y z -
#   2 a1 a4 x^2 y z + a1^2 a2 x y^2 z + a2^2 x y^2 z - a1 a3 x y^2 z +
#   a4 x y^2 z - a1^3 y^3 z - 2 a1 a2 y^3 z + 2 a3 y^3 z + a4^2 x^2 z^2 -
#   a2 a6 x^2 z^2 + a2^2 a3 x y z^2 - 2 a1 a2 a4 x y z^2 + a3 a4 x y z^2 +
#   a1 a6 x y z^2 - 2 a1 a2 a3 y^2 z^2 + a3^2 y^2 z^2 + 3 a1^2 a4 y^2 z^2 +
#   2 a2 a4 y^2 z^2 - 2 a6 y^2 z^2 + a2 a4^2 x z^3 - a2^2 a6 x z^3 -
#   a4 a6 x z^3 + 2 a2 a3 a4 y z^3 - 3 a1 a4^2 y z^3 + 2 a1 a2 a6 y z^3 -
#   2 a3 a6 y z^3 + a4^3 z^4 - 2 a2 a4 a6 z^4 + a6^2 z^4

# We now give some formulae that identify the singular locus.

#Frule = {F  -> HomogeneousCubic,
#         Fx -> D[HomogeneousCubic,x],
#         Fy -> D[HomogeneousCubic,y],
#         Fz -> D[HomogeneousCubic,z]}

#A0 = 4 F - 2 x Fx - (2 y + a3 z) Fy
#A1 = - 2 Fx + a1 Fy
#A2 = 4 a2 z F +
#     - ((a1 a3 + a4) z^2 + (a1^2 + 2 a2) x z + 3 x^2 + a1 y z) Fx +
#     - (a2 a3 z^2 + (a1 a2 + 2 a3) x z + 2 a1 x^2 + (2 a2 z + 4 x) y) Fy
#
#AA0 = 2 x^3 - B4 x z^2 - B6 z^3
#AA1 = 6 x^2 + B2 x z + B4 z^2
#AA2 = x^4 - B4 x^2 z^2 - 2 B6 x z^3 - B8 z^4

#Dl = (-7 B2 B4 z^2 + 27 B6 z^2 - 36 B4 x z + 3 B2 x^2) AA0 +
#     (-8 B4^2 z^3 + 2 B2 B6 z^3 + B2 B4 x z^2 - 9 B6 x z^2 +
#      12 B4 x^2 z - B2 x^3) AA1 +
#      B2^2 z AA2

# The singular point, where Delta = 0 and c4 is invertible.

#xs = 18 b6 - b2 b4

#ys = (a1 a4 - 2 a2 a3) b2 + 12 a3 b4 - 9 a1 b6

#zs = c4

# This evaluates to {0,0,0,0}, showing that [xs:ys:zs] is
# indeed a singular point.

CheckSingular0 =
({F,Fx,Fy,Fz} /. Frule /. {x->xs,y->ys,z->zs}) -
 {-c6,36,0,3 b2} Delta

# The following evaluates to {0,0}.  Because points with z=0
# are smooth and zs = c4 is assumed invertible, we conclude
# that every singular point is projectively equivalent to
# [xs:ys:zs].

CheckSingular1 =
({-2 b2 z Fx -
 (6 a1 x + 12 y - a1 b2 z + 18 a3 z) Fy +
 24 z Fz,
 a1 b2 z Fx +
 (3 a1^2 x + 6 a1 y + (2 a2 b2 - 3 a1 a3 - 24 a4)z ) Fy -
 12 a1 z Fz} /. Frule) -
{ (zs x - xs z) z^2 ,
  (zs y - ys z) z^2}

# Grobner basis for the singular points over the locus where
# c4 = Delta = 2 = 0 (with y > x > a3 > a1 > a6 > a4 > a2)

AdditiveLocusMod2 = {
 y^2 + x^3 + a2 x^2 + a4 x + a6,
 x^2 y + a4 y + a3 x^2 + a2 a3 x + a1 a6 + a3 a4,
 a3 y + x^3 + a4 x,
 a1 y + x^2 + a4,
 x^4 + a3^2 x + a1^2 a6 + a4^2 + a1 a3 a4 + a2 a3^2,
 a3 x^3 + a1^3 a6 + a1 a4^2 + a1^3 a2 a4,
 a1^2 a4^2 + a3^2 x^2,
 a3 + a1 x,
 a3^3 + a1^2 a3 a4,
 a1 a3^2 + a1^3 a4,
 a1^3 a3,
 a1^4}

ReducedAdditiveLocusMod2 = {y^2 + a2 a4 + a6, x^2 + a4, a1, a3}

AdditiveLocusMod3 = {
 y - a1 x - a3,
 a1^4 - a1^2 a2 + a2^2,
 -a3^2 - a6 - a1 a3 x + a4 x - x^3,
 a1 a3 - a4 + a1^2 x + a2 x,
 a1^3 a3 + a1 a2 a3 - a1^2 a4 - a2 a4,
 -(a1^2 a3^2) - a1 a3 a4 - a4^2,
 a1^2 a3^2 + a2 a3^2 + a1^2 a6 + a2 a6 - a1 a3 x^2 + a4 x^2}

ReducedAdditiveLocusMod3 = {
 y - a1 x - a3,
 x^3 + a6 + a3^2,
 a4 - a1 a3,
 a2 + a1^2}

ReducedAdditiveLocusRational = {
 y + a1 x/2 + a3/2,
 x^2 - b2 x/6 - b4/6,
 a6 + a3^2/4 - a2^3/27 - a1^2 a2^2/36 - a1^4 a2/144 - a1^6/1728,
 a4 + a1 a3/2 - a2^2/3 - a1^2 a2/6 - a1^4/48}

#**************************************************************
# Various quotients of localisations of Z[a1,..,a6] that give
# etale maps to the elliptic moduli stack (I think)

# etale1 covers D(6 c6)

etale1 = {a1 -> 0, a2 -> 0, a3 -> 0, a6 -> 1}

# etale2 covers D(6 c4)

etale2 = {a1 -> 0, a3 -> 0, a2 -> 0, a4 -> 1}

# etale3 covers a neighbourhood of V(2) D(c4)

etale3 = {a1 -> 1, a3 -> 0, a2 -> 0, a4 -> 0}

# etale4 covers a neighbourhood of V(3) D(c4)

etale4 = {a1 -> 0, a3 -> 0, a2 -> 1, a4 -> 0}

# etale5 covers a neighbourhood of V(2,a1) ; etale over D(3)

etale5 = {a3 -> 1, a2 -> 0, a4 -> 0, a6 -> 0}

# etale6 covers a neighbourhood of V(3,a2) ; etale over D(2)

etale6 = {a1 -> 0, a3 -> 0, a4 -> 1, a6 -> 0}

# Det[Jac[etale6,a2]] (for example) is the Jacobian
# determinant for the map
#
#  Z[a1,a2,a3,a4,a6] -> Z[v,r,s,t,a2]
#
# (where v = 1/u) given by alpha.  This must be invertible for
# the map to be etale.

Jac[er_,aa_] :=
 Outer[D,
  FixedPoint[Expand[# /. er /. u -> 1/v]&, alpha /@ {a1,a3,a2,a4,a6}],
  {v,r,s,t,aa}
 ]

#**************************************************************
# Singular fibres

# The curve y^2 z + x y z = x^3 is a nodal cubic, with
# multiplicative formal group.

multcurve = {a1->1, a3->0, a2->0, a4->0, a6->0}

# There is a birational map f from P^1 to the curve, with
# inverse g.	This sends both 0 and infinity to the singular
# point [0:0:1], and the restriction to G_m is a homomorphism.
# The discriminant is zero and the j invariant is infinite.

fm[s_,t_] := {s t (s - t), t^2 s, (s - t)^3}

gm[x_,y_,z_] := {y + x, y}

# The curve y^2 = x^3 is a cuspidal cubic, with additive
# formal group.

addcurve = {a1->0, a3->0, a2->0, a4->0, a6->0}

# There is a birational map f from P^1 to the curve, with
# inverse g.	This sends infinity to the singular point
# [0:0:1] with multiplicity two, and the restriction to G_a is
# a homomorphism the discriminant is zero and the j invariant
# is undefined.

fa[s_,t_] := {t^2 s, t^3, s^3}

ga[x_,y_,z_] := {x, y}

#**************************************************************
# Curves with prescribed j invariant

# This curve is defined if jj - 1728 is invertible, and is
# nonsingular if jj is also invertible.  It has
# c4 = - c6 = jj/(jj - 1728) and Delta = jj^2 / (jj - 1728)^3
# and j = jj (unless jj = 0, in which case j is undefined).

# jcurve[0] corresponds to the curve (y + x/2)^2 =(x + 1/12)^3

jcurve[jj_] :=
 {a1 -> 1, a2 -> 0, a3 -> 0,
  a4 -> 36/(1728 - jj), a6 -> 1/(1728 - jj)}

#**************************************************************
# Legendre curve.  This is defined where 2 is invertible and
# lm != 0 or 1.  The points of order two are
# P = [0:0:1] and Q = [1:0:1] and P+Q = [lm:0:1].  An invariant
# differential is given by om = -dx/y.  This is the universal
# example of a curve with an invariant differential and a
# level two structure such that x(P) = 0 and x(Q) = 1, where
# x is the unique choice adapted to om with a1 = a3 = 0.

# c4    = 16(1 - lm + lm^2)
# c6    = 32(lm - 2)(lm + 1)(2 lm - 1)
# Delta = 16 lm^2 (lm - 1)^2
# j     = 256 (1 - lm + lm^2)^3 / ((lm - 1)^2 lm^2)

legendre = {a1 -> 0, a3 -> 0, a2 -> -(lm + 1), a4 -> lm, a6 -> 0}

#**************************************************************
# Jacobi quartic.

# c4    = 2^6 3^4 (dl^2 + 3 ep)
# c6    = 2^9 3^6 (dl^3 - 9 dl ep)
# Delta = 2^12 3^12 ep (ep - dl^2)^2
# j     = 64 (dl^2 + 3 ep)^3 / (ep (ep - dl^2)^2)

jacobi_rule :=
 {a1 = 0, a3 = 0, a2 = 0, a4 = -108 dl^2 - 324 ep, a6 = 3888 dl ep - 432 dl^3}

xXY := (6*(-2*dl - dl^2*X^2 + 3*ep*X^2 + 2*dl*Y))/(-1 + dl*X^2 + Y);
yXY := (108*(dl^2 - ep)*X)/(-1 + dl*X^2 + Y);

Xxy = (6*(12*dl - x))/y
Yxy = (2592*dl^3 - 7776*dl*ep + 216*dl^2*x + 648*ep*x -
       36*dl*x^2 + y^2)/y^2

# In quartic form, the zero element is (0,1).

jacobiquartic = Y^2 - (1 - 2 dl X^2 + ep X^4)

# invariant differential

omegaXY = -dX/(6 Y)

# This can also be written as \sum_n P_n(dl,ep) X^(2n) dX
# where P_n(dl,ep)=P_n(dl/Sqrt[ep]) ep^(n/2) and P_n(t) is the
# Legendre polynomial.  These are defined by
# (1 -2xt +t^2)^(-1/2) = \sum_n P_n(x) t^n

# There is a canonical point P of order 2: (x,y)=(12 dl,0).
# Apparently this is the universal example of a curve with an
# invariant differential and a point of exact order 2 (where
# 2 is invertible).  Apparently also
# div(X) = [2]^{-1}[0] - [2]^{-1}[P]

# It seems that if six is invertible and we have a given point
# P of order 2, we can put the curve in the form
# y^2 = x^3 + a4 x + a6 and then P = (12 dl,0) say and we can
# define ep = -(4 a4 + 432 dl^2)/1296 to put the curve in
# Jacobi form.  I'm not sure this is right, though.

#**************************************************************

# Formal group formulae, valid up to terms of weight 7.

F = x0 + x1 + a1*x0*x1 - a2*x0^2*x1 + 2*a3*x0^3*x1 - 2*a1*a3*x0^4*x1 -
 2*a4*x0^4*x1 + 2*a1^2*a3*x0^5*x1 + 2*a2*a3*x0^5*x1 + 2*a1*a4*x0^5*x1 -
 2*a1^3*a3*x0^6*x1 - 4*a1*a2*a3*x0^6*x1 - 2*a3^2*x0^6*x1 -
 2*a1^2*a4*x0^6*x1 - 2*a2*a4*x0^6*x1 - 3*a6*x0^6*x1 - a2*x0*x1^2 -
 a1*a2*x0^2*x1^2 + 3*a3*x0^2*x1^2 + a2^2*x0^3*x1^2 - a1*a3*x0^3*x1^2 -
 4*a4*x0^3*x1^2 + a1^2*a3*x0^4*x1^2 + a1*a4*x0^4*x1^2 - a1^3*a3*x0^5*x1^2 -
 a1*a2*a3*x0^5*x1^2 - a1^2*a4*x0^5*x1^2 - 9*a6*x0^5*x1^2 + 2*a3*x0*x1^3 +
 a2^2*x0^2*x1^3 - a1*a3*x0^2*x1^3 - 4*a4*x0^2*x1^3 + a1*a2^2*x0^3*x1^3 +
 a1^2*a3*x0^3*x1^3 - 2*a2*a3*x0^3*x1^3 - a2^3*x0^4*x1^3 - a1^3*a3*x0^4*x1^3 -
 2*a1*a2*a3*x0^4*x1^3 + 4*a3^2*x0^4*x1^3 - a1^2*a4*x0^4*x1^3 +
 4*a2*a4*x0^4*x1^3 - 15*a6*x0^4*x1^3 - 2*a1*a3*x0*x1^4 - 2*a4*x0*x1^4 +
 a1^2*a3*x0^2*x1^4 + a1*a4*x0^2*x1^4 - a2^3*x0^3*x1^4 - a1^3*a3*x0^3*x1^4 -
 2*a1*a2*a3*x0^3*x1^4 + 4*a3^2*x0^3*x1^4 - a1^2*a4*x0^3*x1^4 +
 4*a2*a4*x0^3*x1^4 - 15*a6*x0^3*x1^4 + 2*a1^2*a3*x0*x1^5 + 2*a2*a3*x0*x1^5 +
 2*a1*a4*x0*x1^5 - a1^3*a3*x0^2*x1^5 - a1*a2*a3*x0^2*x1^5 -
 a1^2*a4*x0^2*x1^5 - 9*a6*x0^2*x1^5 - 2*a1^3*a3*x0*x1^6 -
 4*a1*a2*a3*x0*x1^6 - 2*a3^2*x0*x1^6 - 2*a1^2*a4*x0*x1^6 - 2*a2*a4*x0*x1^6 -
 3*a6*x0*x1^6

logF =
 x - (a1*x^2)/2 + (a1^2*x^3)/3 + (a2*x^3)/3 - (a1^3*x^4)/4 - (a1*a2*x^4)/2 -
 (a3*x^4)/2 + (a1^4*x^5)/5 + (3*a1^2*a2*x^5)/5 + (a2^2*x^5)/5 +
 (6*a1*a3*x^5)/5 + (2*a4*x^5)/5 - (a1^5*x^6)/6 - (2*a1^3*a2*x^6)/3 -
 (a1*a2^2*x^6)/2 - 2*a1^2*a3*x^6 - a2*a3*x^6 - a1*a4*x^6 + (a1^6*x^7)/7 +
 (5*a1^4*a2*x^7)/7 + (6*a1^2*a2^2*x^7)/7 + (a2^3*x^7)/7 +
 (20*a1^3*a3*x^7)/7 + (24*a1*a2*a3*x^7)/7 + (6*a3^2*x^7)/7 +
 (12*a1^2*a4*x^7)/7 + (6*a2*a4*x^7)/7 + (3*a6*x^7)/7

# ms[k] is the [k] series.

ms[-1] =
 -x + a1*x^2 - a1^2*x^3 + a1^3*x^4 + a3*x^4 - a1^4*x^5 - 3*a1*a3*x^5 +
 a1^5*x^6 + 6*a1^2*a3*x^6 + a2*a3*x^6 - a1^6*x^7 - 10*a1^3*a3*x^7 -
 4*a1*a2*a3*x^7 - 2*a3^2*x^7 + a1^7*x^8 + 15*a1^4*a3*x^8 +
 10*a1^2*a2*a3*x^8 + a2^2*a3*x^8 + 10*a1*a3^2*x^8 + a3*a4*x^8 - a1^8*x^9 -
 21*a1^5*a3*x^9 - 20*a1^3*a2*a3*x^9 - 5*a1*a2^2*a3*x^9 - 30*a1^2*a3^2*x^9 -
 5*a2*a3^2*x^9 - 5*a1*a3*a4*x^9

ms[2] =
 2*x + a1*x^2 - 2*a2*x^3 - a1*a2*x^4 + 7*a3*x^4 + 2*a2^2*x^5 - 6*a1*a3*x^5 -
 12*a4*x^5 + a1*a2^2*x^6 + 7*a1^2*a3*x^6 + 2*a2*a3*x^6 + 6*a1*a4*x^6 -
 2*a2^3*x^7 - 8*a1^3*a3*x^7 - 14*a1*a2*a3*x^7 + 4*a3^2*x^7 - 8*a1^2*a4*x^7 +
 4*a2*a4*x^7 - 54*a6*x^7

ms[3] =
 3*x + 3*a1*x^2 + a1^2*x^3 - 8*a2*x^3 - 12*a1*a2*x^4 + 39*a3*x^4 -
 6*a1^2*a2*x^5 + 24*a2^2*x^5 - 9*a1*a3*x^5 - 96*a4*x^5 - a1^3*a2*x^6 +
 44*a1*a2^2*x^6 + 30*a1^2*a3*x^6 - 57*a2*a3*x^6 - 48*a1*a4*x^6 +
 30*a1^2*a2^2*x^7 - 72*a2^3*x^7 - 36*a1^3*a3*x^7 - 168*a1*a2*a3*x^7 +
 234*a3^2*x^7 - 72*a1^2*a4*x^7 + 288*a2*a4*x^7 - 936*a6*x^7

ms[5] =
 5*x + 10*a1*x^2 + 10*a1^2*x^3 - 40*a2*x^3 + 5*a1^3*x^4 - 140*a1*a2*x^4 +
 310*a3*x^4 + a1^4*x^5 - 222*a1^2*a2*x^5 + 376*a2^2*x^5 + 306*a1*a3*x^5 -
 1248*a4*x^5 - 205*a1^3*a2*x^6 + 1740*a1*a2^2*x^6 + 620*a1^2*a3*x^6 -
 2130*a2*a3*x^6 - 3120*a1*a4*x^6 - 120*a1^4*a2*x^7 + 3750*a1^2*a2^2*x^7 -
 3560*a2^3*x^7 - 90*a1^3*a3*x^7 - 9540*a1*a2*a3*x^7 + 10540*a3^2*x^7 -
 5800*a1^2*a4*x^7 + 14240*a2*a4*x^7 - 33480*a6*x^7

Null[];


######################################################################
#
# General operad framework.

`check_semi_gamma_args` := proc(J,U,V,is_el_CC)
 local i,k,jj;
 global reason;

 if not type(J,list(nonnegint)) then
  reason := [convert(procname,string),"J is not a list of natural numbers",J];
  return false;
 fi;

 k := nops(J);
 jj := `+`(op(JJ));

 if not is_el_CC(k)(U) then
  reason := [convert(procname,string),"U is not in C(k)",eval(U),k];
  return false;
 fi;

 if not (type(V,list) and nops(V) = k) then
  reason := [convert(procname,string),"V is not a list of length k",eval(V),k];
  return false;
 fi;

 for i from 1 to k do
  if not is_el_CC(J[i])(V[i]) then
   reason := [convert(procname,string),"V[i] is not in C(j[i])",i,j[i],eval(V[i])];
   return false;
  fi;
 od;

 return true;
end;

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

`check_semi_gamma_axiom` := proc(L,U,V,W,is_el_CC,is_equal_CC,gamma_CC)
 local J,L_,k,jj,ii,H,k0,j0,W0,W_,UV,VW0,VW,UVW0,UVW1;
 global reason;

 if not type(L,list(list(nonnegint))) then
  reason := [convert(procname,string),"L is not a list of lists of natural numbers",L];
  return false;
 fi;

 J := map(nops,L);
 L_ := map(op,L);
 k := nops(J);
 jj := `+`(op(J));
 ii := `+`(op(L_));
 H := [seq(`+`(op(L[k0])),k0=1..k)];

 if not is_el_CC(k)(U) then
  reason := [convert(procname,string),"U is not in O(k)",eval(U),k];
  return false;
 fi;

 if not (type(V,list) and nops(V) = k) then
  reason := [convert(procname,string),"V is not a list of length k",eval(V),k];
  return false;
 fi;

 for k0 from 1 to k do
  if not is_el_CC(J[k0])(V[k0]) then
   reason := [convert(procname,string),"V[k0] is not in O(J[k0])",k0,J[k0],eval(V[k0])];
   return false;
  fi;
 od;

 if not (type(W,list) and nops(W) = k) then
  reason := [convert(procname,string),"W is not a list of length k",eval(W),k];
  return false;
 fi;

 for k0 from 1 to k do
  W0 := W[k0];
  if not (type(W0,list) and nops(W0) = J[k0]) then
   reason := [convert(procname,string),"W[k0] is not a list of length J[k0]",k0,J[k0],W0];
   return false;
  fi;

  for j0 from 1 to J[k0] do
   if not is_el_CC(L[k0][j0])(W[k0][j0]) then
    reason := [convert(procname,string),"W[k0][j0] is not in O(L[k0][j0])",k0,j0,L[k0][j0],eval(W[k0][j0])];
    return false;
   fi;
  od:
 od:

 UV := gamma_CC(J)(U,V);
 if not is_el_CC(jj)(UV) then
  reason := [convert(procname,string),"UV is not in O(jj)",jj,J,eval(U),eval(V),eval(UV)];
  return false;
 fi;

 W_ := map(op,W);

 if not `check_semi_gamma_args`(L_,UV,W_,is_el_CC) then
  reason := [convert(procname,string),"invalid arguments to gamma 1",reason];
  return false;
 fi;

 UVW0 := gamma_CC(L_)(UV,W_);
 if not is_el_CC(ii)(UVW0) then
  reason := [convert(procname,string),"UVW0 is not in O(ii)",ii,eval(UVW0)];
  return false;
 fi;

 VW := [];
 for k0 from 1 to k do
  if not `check_semi_gamma_args`(L[k0],V[k0],W[k0],is_el_CC) then
   reason := [convert(procname,string),"invalid arguments to gamma 2",reason];
   return false;
  fi;
  VW0 := gamma_CC(L[k0])(V[k0],W[k0]);
  if not is_el_CC(H[k0])(VW0) then
   reason := [convert(procname,string),"VW[k0] is not in O(H[k0])",k0,H[k0],eval(VW0)];
   return false;
  fi;
  VW := [op(VW),VW0];
 od;

 if not `check_semi_gamma_args`(H,U,VW,is_el_CC) then
  reason := [convert(procname,string),"invalid arguments to gamma 3",reason];
  return false;
 fi;

 UVW1 := gamma_CC(H)(U,VW);
 if not is_el_CC(ii)(UVW1) then
  reason := [convert(procname,string),"UVW1 is not in O(ii)",ii,eval(UVW1)];
  return false;
 fi;

 if not is_equal_CC(ii)(UVW0,UVW1) then
  reason := [convert(procname,string),"UVW0 != UVW1",eval(UVW0),eval(UVW1)];
  return false;
 fi;

 return true;
end;


read("metaMPFR_common.mpl"):

FUNCTION_SERIES := 0:
FUNCTION_SERIES_RATIONAL := %+1:
CONSTANT_SERIES := %+1:

###############################################################################
######################### We can now generate the code ########################
###############################################################################
# This procedure generate code for a straightforward evaluation of the series
# corresponding to a recurrence.
#   rec is a recurrence with inital conditions
#   type can take three values, depending on which series should be evaluated:
#     FUNCTION_SERIES -> produces code for evaluating sum( a(i)*x^i )
#     FUNCTION_SERIES_RATIONAL -> produces code for evaluating sum( a(i)*(p/q)^i )
#     CONSTANT_SERIES -> produces code for evaluating sum( a(i) )
#   name is the name the should be given to the produced procedure.
#   fofx is an optional parameter. It is the function being implemented.
#     -> if provided, this argument will be used to (heuristically) find the limits
#        of the function at +/-oo and find its asymptotical behavior.
#   *IMPORTANT NOTE*: it must be a function of the variable 'x'.
#                     Moreover, neither _f nor x shall be assigned at the time of
#                     calling the function
#
generateStraightforwardAlgo := proc(rec, aofn, type, name, filename, fofx := _f(x))
  local a, b, n, fd, init_cond, nc, d, exponents, formalRec, c, s1, s2, p, q, hardconstant, f0, i0, ri0, i, j, var, var1, var2, var3, guard_bits, required_mulsi, required_divsi, temp, error_counter, error_in_loop, maxofti, additional_error:

  getname(aofn, a, n):

  required_mulsi := {}:
  required_divsi := {}:
  fd := fopen(filename, WRITE):

  # We check that we have a recurrence of the form a(n+d)=r(n)*a(n)
  # and we extract its canonical form:
  #    a(n+d) = c * s1(n)/s1(n+d) * s2(n+d)/s2(n) * p(n)/q(n) * a(n)
  #
  formalRec := checkOneTermRecurrence(rec, a(n)):
  decomposeOneTermRecurrence(formalRec, c, s1, s2, p, q):
  d := formalRec:-order:

  # We keep only non-trivial inital conditions
  init_cond := removeTrivialConditions(rec, a(n));
  nc := nops(init_cond):
  exponents := {1, d}:
  for i from 1 to nc do
    exponents := { op(exponents), init_cond[i][1] }
  od:
  exponents := findFixpointOfDifferences(exponents):
  exponents := exponents minus {0}:

  error_counter := []:

  fprintf(fd, "/* Evaluation by a straightforward algorithm */\n"):
  fprintf(fd, "/* Code automatically generated by metaMPFR. */\n"):

  fprintf(fd, "static int\n"):
  if (type=FUNCTION_SERIES)
    then fprintf(fd, "mpfr_%a (mpfr_ptr res, mpfr_srcptr x, mpfr_rnd_t rnd)\n", name):
  elif (type=FUNCTION_SERIES_RATIONAL)
    then fprintf(fd, "mpfr_%a (mpfr_ptr res, int u, int v, mpfr_rnd_t rnd)\n", name):
  elif (type=CONSTANT_SERIES)
    then fprintf(fd, "mpfr_%a (mpfr_ptr res, mpfr_rnd_t rnd)\n", name):
  fi:
  fprintf(fd, "{\n"):

  ######################################################
  #################### Declarations ####################
  ######################################################

  fprintf(fd, "  MPFR_ZIV_DECL (loop);\n"):
  fprintf(fd, "  MPFR_SAVE_EXPO_DECL (expo);\n"):
  fprintf(fd, "  mpfr_prec_t wprec;             /* working precision */\n"):
  fprintf(fd, "  mpfr_prec_t prec;              /* target precision */\n"):
  fprintf(fd, "  mpfr_prec_t err;               /* used to estimate the evaluation error */\n"):
  fprintf(fd, "  mpfr_prec_t correctBits;       /* estimates the number of correct bits*/\n"):
  fprintf(fd, "  unsigned long int k;\n"):
  fprintf(fd, "  unsigned long int conditionNumber;        /* condition number of the series */\n"):
  fprintf(fd, "  unsigned assumed_exponent;     /* used as a lowerbound of -EXP(f(x)) */\n"):
  fprintf(fd, "  int r;                         /* returned ternary value */\n"):
  fprintf(fd, "  mpfr_t s;                      /* used to store the partial sum */\n"):

  if (whattype(c) = 'fraction') or (whattype(c) = 'integer')
    then hardconstant := 0
    else hardconstant := 1
  fi:
  if (type=FUNCTION_SERIES) then hardconstant := 1 fi:

  if (hardconstant=1)
  then
    if (type=CONSTANT_SERIES)
      then fprintf(fd, "  mpfr_t x%d;                     /* used to store %a */\n", d, c):
    elif (type=FUNCTION_SERIES_RATIONAL)
      then fprintf(fd, "  mpfr_t x%d;                     /* used to store %a */\n", d, c*(u/v)^d):
    elif (type=FUNCTION_SERIES)
      then fprintf(fd, "  mpfr_t x%d;                     /* used to store %a */\n", d, c*x^d):
    fi:
  fi:

  if (type=FUNCTION_SERIES)
  then
    fprintf(fd, "  mpfr_t tmp;\n"):
    if (nops(exponents)-1 >= 1) then fprintf(fd, "  mpfr_t ") fi:
    for i from 1 to nops(exponents)-1 do
      fprintf(fd, "x%d", exponents[i]):
      if (i<nops(exponents)-1) then fprintf(fd, ", ") else fprintf(fd, ";                     /* used to store x^i */\n") fi
    od:
  fi:

  fprintf(fd, "  mpfr_t ");
  for i from 1 to nc do
    fprintf(fd, "tip%d", init_cond[i][1]):
    if (i<nc) then fprintf(fd, ", ") else fprintf(fd, "; /* used to store successive values of t_i */\n") fi:
  od:
  fprintf(fd, "  int "):
  for i from 1 to nc do
    fprintf(fd, "test%d", init_cond[i][1]):
    if (i<nc) then fprintf(fd, ", ") else fprintf(fd, ";\n") fi:
  od:
  fprintf(fd, "  int global_test;               /* used to test when the sum can be stopped */"):

  fprintf(fd, "\n"):
  fprintf(fd, "  /* Logging */\n"):
  if (type=FUNCTION_SERIES)
    then fprintf(fd, "  MPFR_LOG_FUNC ( (\"x[%%#R]=%%R rnd=%%d\", x, x, rnd), (\"res[%%#R]=%%R\", res, res) );\n\n")
  elif (type=FUNCTION_SERIES_RATIONAL)
    then fprintf(fd, "  MPFR_LOG_FUNC ( (\"x=u/v with u=%%d and v=%%d, rnd=%%d\", u, v, rnd), (\"res[%%#R]=%%R\", res, res) );\n\n")
  else fprintf(fd, "  MPFR_LOG_FUNC ( (\"rnd=%%d\", rnd), (\"res[%%#R]=%%R\", res, res) );\n\n")
  fi:

  ######################################################
  #################### Special cases ###################
  ######################################################

  if ( (type=FUNCTION_SERIES) or (type=FUNCTION_SERIES_RATIONAL) )
  then
    fprintf(fd, "  /* Special cases */\n"):

    if (type=FUNCTION_SERIES)
      then fprintf(fd, "  if (MPFR_UNLIKELY (MPFR_IS_NAN (x)))\n")
      else fprintf(fd, "  if (MPFR_UNLIKELY (v==0))\n")
    fi:
    fprintf(fd, "    {\n"):
    fprintf(fd, "      MPFR_SET_NAN (res);\n"):
    fprintf(fd, "      MPFR_RET_NAN;\n"):
    fprintf(fd, "    }\n"):

    if (init_cond[1][1] > 0) then f0 := 0 else f0 := init_cond[1][2] fi:
    if (type=FUNCTION_SERIES)
      then fprintf(fd, "  if (MPFR_UNLIKELY (MPFR_IS_ZERO (x)))\n")
      else fprintf(fd, "  if (MPFR_UNLIKELY (u==0))\n")
    fi:
    if (whattype(f0) = 'integer')
    then
      fprintf(fd, "    {\n"):
      fprintf(fd, "      return mpfr_set_si (res, %a, rnd);\n", f0):
      fprintf(fd, "    }\n")
    else
      printf("You need to provide a function mpfr_%a0(mpfr_t, mpfr_rnd_t) that evaluates %a with correct rounding\n", name, f0):
      fprintf(fd, "    {\n"):
      fprintf(fd, "      return mpfr_%a0 (res, rnd);\n", name):
      fprintf(fd, "    }\n")
    fi:

    if (type=FUNCTION_SERIES)
    then
      fprintf(fd, "  if (MPFR_UNLIKELY (MPFR_IS_INF (x)))\n"):
      fprintf(fd, "    {\n"):
      for i from -1 to 1 by 2 do  # Trick to handle both -oo and +oo
        if (i<0)
          then fprintf(fd, "      if (MPFR_IS_NEG (x))\n"):
          else fprintf(fd, "      else\n")
        fi:
        fprintf(fd, "        {\n"):
        if (i<0) then f0 := "m" else f0 := "p" fi:
        if (fofx <> _f(x))
        then
          f0 := limit(fofx, x=i*infinity):
          if (whattype(f0)='integer')
          then fprintf(fd, "          return mpfr_set_si (res, %a, rnd);\n", f0):
          elif (f0 = infinity) or (f0 = -infinity)
          then
            fprintf(fd, "          MPFR_SET_INF(res);\n"):
            if (f0>0)
            then fprintf(fd, "          MPFR_SET_POS(res);\n")
            else fprintf(fd, "          MPFR_SET_NEG(res);\n")
            fi:
            fprintf(fd, "          MPFR_RET(0);\n"):
          else
            if (i<0) then f0 := "m" else f0 := "p" fi:
          fi:
        fi:
        if ((f0 = "p") or (f0 = "m"))
        then
          printf("You need to provide a function mpfr_%a%sinf(mpfr_t, mpfr_rnd_t) that evaluates lim(f(x), x=", name, f0):
          if (f0 = "m") then printf("-") fi:
          printf("inf) with correct rounding.\n"):
          fprintf(fd, "          return mpfr_%a%sinf (res, rnd);\n", name, f0)
        fi:
        fprintf(fd, "        }\n")
      od:
      fprintf(fd, "    }\n")
    fi:
  fi:

  ######################################################
  ################## Precomputations ###################
  ######################################################

  fprintf(fd, "\n"):
  fprintf(fd, "  /* Save current exponents range */\n"):
  fprintf(fd, "  MPFR_SAVE_EXPO_MARK (expo);\n\n"):

  if ( (type=FUNCTION_SERIES) or (type=FUNCTION_SERIES_RATIONAL) )
  then  fprintf(fd, "  /* FIXME: special case for large values of |x| ? */\n\n")
  fi:

  # Note : prec is the value such that we will try to compute an approximation
  # with relative error smaller than 2^(1-prec).
  # Several things may happen:
  #   1) We do not achieve the intended error: this is because we badly estimated the exponent of the result
  #   2) We achieve the error, but it is not sufficient to decide correct rounding (Ziv's bad case)
  # Against 1), we can try to make our estimate of the exponent better with any heuristic.
  # Against 2), we can consider more guard bits. 11 guard bits seem a good value for the beginning
  #   (statistically, we expect to fail in less than 0.1 % of the cases)
  # wprec is the precision used during the computation, in order to ensure the final relative error 2^(1-prec)
  #
  fprintf(fd, "  /* We begin with 11 guard bits */\n"):
  fprintf(fd, "  prec = MPFR_PREC (res) + 11;\n"):
  fprintf(fd, "  MPFR_ZIV_INIT (loop, prec);\n"):

  # TODO: the value here can be chosen completely heursitically. We could do something much better
  # when fofx is known by using asympt(fofx, x, 1). A clean implementation appears to be complex though.
  # We must catch errors if the development does not exist (e.g. AiryAi(-x));
  # We must find a separation after which the asymptotic behavior is valid (e.g. x>1)
  printf("The code contains a variable assumed_exponent arbitrarily set to 10. You can put any value heuristically chosen. The closer it is to -log_2(|f(x)|), the better it is.\n"):
  fprintf(fd, "  assumed_exponent = 10; /* TIP: You can put any value heuristically chosen. The closer it is to -log_2(|f(x)|), the better it is */\n"):

  # TODO: find a way of putting a rigorous value here.
  # This value *must* be rigorous: the safety of the implementation relies on it.
  # Precisely, we need to have sum(|a(i)*x^i|) <= 2^conditionNumber
  fprintf(fd, "  conditionNumber = xxx; /* FIXME: set a value such that sum(|a(i)*x^i|) <= 2^conditionNumber */\n"):
  printf("The code contains a variable conditionNumber that you must manually set to a suitable value, in order to ensure that sum_{i=0}^{infinity} |a(i)*x^i| <= 2^conditionNumber\n"):
  fprintf(fd, "  wprec = prec + ERRORANALYSISPREC + conditionNumber + assumed_exponent;\n"):

  ######################################################
  ################## Initialisations ###################
  ######################################################

  if (hardconstant=1)
  then
    fprintf(fd, "  mpfr_init (x%d);\n", d):
  fi:
  if (type = FUNCTION_SERIES)
  then
    fprintf(fd, "  mpfr_init (tmp);\n"):
    for i from 1 to nops(exponents)-1 do
      fprintf(fd, "  mpfr_init (x%d);\n", exponents[i])
    od:
  fi:

  for i from 1 to nc do
    fprintf(fd, "  mpfr_init (tip%d);\n", init_cond[i][1])
  od:
  fprintf(fd, "  mpfr_init (s);\n\n"):

  ######################################################
  ########## Ziv' loop: setting the precision ##########
  ######################################################

  fprintf(fd, "  /* ZIV' loop */\n"):
  fprintf(fd, "  for (;;)\n"):
  fprintf(fd, "    {\n"):

  fprintf(fd, "      MPFR_LOG_MSG ((\"Working precision: %%d\\n\", wprec, 0));\n\n"):
  if (hardconstant=1)
  then
    fprintf(fd, "      mpfr_set_prec (x%d, wprec);\n", d):
  fi:
  if (type = FUNCTION_SERIES)
  then
    fprintf(fd, "      mpfr_set_prec (tmp, wprec);\n"):
    fprintf(fd, "      if(mpfr_get_prec (x) > wprec)\n"):
    fprintf(fd, "        mpfr_set_prec (x1, wprec);\n"):
    fprintf(fd, "      else\n"):
    fprintf(fd, "        mpfr_set_prec (x1, mpfr_get_prec (x));\n"):
    for i from 2 to nops(exponents)-1 do
      fprintf(fd, "      mpfr_set_prec (x%d, wprec);\n", exponents[i])
    od:
  fi:

  for i from 1 to nc do
    fprintf(fd, "      mpfr_set_prec (tip%d, wprec);\n", init_cond[i][1])
  od:
  fprintf(fd, "      mpfr_set_prec (s, wprec);\n\n"):

  ######################################################
  ############ Ziv' loop: initial conditions ###########
  ######################################################

  fprintf(fd, "      mpfr_set_ui (s, 0, MPFR_RNDN);\n"):

  if (type = FUNCTION_SERIES)
  then
    fprintf(fd, "      mpfr_set (x1, x, MPFR_RNDN);\n"):
    error_counter := init_error_counter("x1", error_counter):
    for i from 2 to nops(exponents) do
        fprintf(fd, "      mpfr_mul (x%d, x%d, x%d, MPFR_RNDN);\n", exponents[i], exponents[i-1], exponents[i]-exponents[i-1]):
	var1 := sprintf("x%d", exponents[i]):
        var2 := sprintf("x%d", exponents[i-1]):
        var3 := sprintf("x%d", exponents[i]-exponents[i-1]):
	error_counter := error_counter_of_a_multiplication(var1, var2, var3, error_counter):
    od:
  fi:

  for i from 1 to nc do
    i0 := init_cond[i][1]:
    ri0 := init_cond[i][2]:  # We implement t_{i0} <- ri0
    if (whattype(ri0)='integer') or (whattype(ri0)='fraction')
    then
      if (type = FUNCTION_SERIES) and (i0 <> 0)
        then
          var :=  sprintf("      mpfr_mul_si (tip%d, x%d, ", i0, i0):
          var1 := sprintf("tip%d", i0):
          var2 := sprintf("x%d", i0):
          error_counter := error_counter_of_a_multiplication(var1, var2, "", error_counter):
        else
          var := sprintf("      mpfr_set_si (tip%d, ", i0):
          var1 := sprintf("tip%d", i0):
          error_counter := init_error_counter(var1, error_counter):
      fi:
      fprintf(fd, "%s%d, MPFR_RNDN);\n", var, numer(ri0)):
      if (whattype(ri0)='fraction')
      then
        fprintf(fd, "      mpfr_div_si (tip%d, tip%d, %d, MPFR_RNDN);\n", i0, i0, denom(ri0)):
        var1 := sprintf("tip%d", i0):
        error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      fi:
    else
      printf("You need to provide a function mpfr_%a%d (mpfr_t, mpfr_rnd_t) that evaluates %a with faithful rounding.\n", name, i0, ri0):
      fprintf(fd, "      mpfr_%a%d (tip%d, MPFR_RNDN);\n", name, i0, i0):
      var1 := sprintf("tip%d", i0):
      error_counter := init_error_counter(var1, error_counter):
      if (type = FUNCTION_SERIES) and (i0 <> 0)
      then
        fprintf(fd, "      mpfr_mul (tip%d, tip%d, x%d, MPFR_RNDN);\n", i0, i0, i0):
        var1 := sprintf("tip%d", i0):
        var2 := sprintf("x%d", i0):
        error_counter := error_counter_of_a_multiplication(var1, var1, var2, error_counter):
      fi:
    fi:

    if (type = FUNCTION_SERIES_RATIONAL) and (i0 <> 0)
    then
      var1 := sprintf("tip%d", i0):
      if (i0 = 1)
      then fprintf(fd, "      mpfr_mul_si (tip%d, tip%d, ", i0, i0):
      else
        required_mulsi := { op(required_mulsi), i0 }:
        fprintf(fd, "      mpfr_mul_si%d (tip%d, tip%d, ", i0, i0, i0):
      fi:
      for j from 1 to i0 do
        fprintf(fd, "u, "):
        error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      od:
      fprintf(fd, "MPFR_RNDN);\n"):
      if (i0 = 1)
      then fprintf(fd, "      mpfr_div_si (tip%d, tip%d, ", i0, i0):
      else
        required_divsi := { op(required_divsi), i0 }:
        fprintf(fd, "      mpfr_div_si%d (tip%d, tip%d, ", i0, i0, i0):
      fi:
      for j from 1 to i0 do
        fprintf(fd, "v, "):
        error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      od:
      fprintf(fd, "MPFR_RNDN);\n"):
    fi:

    fprintf(fd, "      mpfr_add (s, s, tip%d, MPFR_RNDN);\n\n", i0):
  od:

  if (whattype(c) = 'integer') or (whattype(c) = 'fraction')
  then
    if (type = FUNCTION_SERIES) and (c = -1)
    then
      fprintf(fd, "      MPFR_CHANGE_SIGN (x%d);\n", d):
    elif (type = FUNCTION_SERIES) and (c <> 1)
    then
      fprintf(fd, "      mpfr_mul_si (x%d, x%d, %d, MPFR_RNDN);\n", d, d, numer(c)):
      var1 := sprintf("x%d", d):
      error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      if (whattype(c) = 'fraction')
      then
        fprintf(fd, "      mpfr_div_si (x%d, x%d, %d, MPFR_RNDN);\n", d, d, denom(c)):
        error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      fi:
    fi:
  else
    printf("You need to provide a function mpfr_%a_cste (mpfr_t, mpfr_rnd_t) that evaluates %a with faithful rounding.\n", name, c):
    if (type = CONSTANT_SERIES)
    then
      fprintf(fd, "      mpfr_%a_cste (x%d, MPFR_RNDN);\n", name, d):
      var1 := sprintf("x%d", d):
      error_counter := init_error_counter(var1, error_counter):
    elif (type = FUNCTION_SERIES) then
      fprintf(fd, "      mpfr_%a_cste (tmp, MPFR_RNDN);\n", name):
      error_counter := init_error_counter("tmp", error_counter):
      fprintf(fd, "      mpfr_mul (x%d, tmp, x%d, MPFR_RNDN);\n", d, d):
      var1 := sprintf("x%d", d):
      error_counter := error_counter_of_a_multiplication(var1, "tmp", var1, error_counter):
    elif (type = FUNCTION_SERIES_RATIONAL) then
      fprintf(fd, "      mpfr_%a_cste (x%d, MPFR_RNDN);\n", name, d):
      var1 := sprintf("x%d", d):
      error_counter := init_error_counter(var1, error_counter):
      if (d = 1)
      then fprintf(fd, "      mpfr_mul_si (x%d, x%d, ", d, d):
      else
        required_mulsi := { op(required_mulsi), d }:
        fprintf(fd, "      mpfr_mul_si%d (x%d, x%d, ", d, d, d):
      fi:
      for j from 1 to d do
        fprintf(fd, "u, "):
        error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      od:
      fprintf(fd, "MPFR_RNDN);\n"):
      var1 := sprintf("x%d", d):
      if (d = 1)
      then fprintf(fd, "      mpfr_div_si (x%d, x%d, ", d, d):
      else
        required_divsi := { op(required_divsi), d }:
        fprintf(fd, "      mpfr_div_si%d (x%d, x%d, ", d, d, d):
      fi:
      for j from 1 to d do
        fprintf(fd, "v, "):
        error_counter := error_counter_of_a_multiplication(var1, var1, "", error_counter):
      od:
      fprintf(fd, "MPFR_RNDN);\n"):
    fi:
  fi:

  ######################################################
  ######### Ziv' loop: evaluation of the series ########
  ######################################################

  fprintf(fd, "\n"):
  fprintf(fd, "      /* Evaluation of the series */\n"):
  fprintf(fd, "      k = %d;\n", d):
  fprintf(fd, "      for (;;)\n"):
  fprintf(fd, "        {\n"):
  if (init_cond[1][1] <> 0) then fprintf(fd, "          k += %d;\n", init_cond[1][1]) fi:

  for i from 1 to nc do
    error_in_loop := 0:
    i0 := init_cond[i][1]:
    if (hardconstant = 1)
    then
      fprintf(fd, "          mpfr_mul (tip%d, tip%d, x%d, MPFR_RNDN);\n", i0, i0, d):
      var1 := sprintf("x%d", d):
      error_in_loop := error_in_loop + 1 + find_in_error_counter(var1, error_counter):
    else
      var := sprintf("tip%d", i0):
      temp := generate_multiply_rational(fd, var, var, c, [[var, error_in_loop]], "          "):
      error_in_loop := find_in_error_counter(var, temp):
      if (type = FUNCTION_SERIES_RATIONAL)
      then
        if (d=1)
        then
          fprintf(fd, "          mpfr_mul_si (tip%d, tip%d, u, MPFR_RNDN);\n", i0, i0):
          fprintf(fd, "          mpfr_div_si (tip%d, tip%d, v, MPFR_RNDN);\n", i0, i0):
	  error_in_loop := error_in_loop + 2:
        else
          required_mulsi := { op(required_mulsi), d }:
          fprintf(fd, "          mpfr_mul_si%d (tip%d, tip%d", d, i0, i0):
          for j from 1 to d do fprintf(fd, ", u") od:
          fprintf(fd, ", MPFR_RNDN);\n"):
	  error_in_loop := error_in_loop + d:

          required_divsi := { op(required_divsi), d }:
          fprintf(fd, "          mpfr_div_si%d (tip%d, tip%d", d, i0, i0):
          for j from 1 to d do fprintf(fd, ", v") od:
          fprintf(fd, ", MPFR_RNDN);\n" ):
	  error_in_loop := error_in_loop + d:
        fi
      fi
    fi:
    var := sprintf("tip%d", i0):

    temp := generate_multiply_poly(fd, var, var, subs(n=k-d, p/q), [[var, error_in_loop]], "          "):
    required_mulsi := { op(required_mulsi), op(temp[1]) }:
    required_divsi := { op(required_divsi), op(temp[2]) }:
    error_in_loop := find_in_error_counter(var, temp[3]):
    temp := generate_multiply_poly(fd, "tmp", var, subs(n=k, s2/s1), [[var, error_in_loop]], "          "):
    required_mulsi := { op(required_mulsi), op(temp[1]) }:
    required_divsi := { op(required_divsi), op(temp[2]) }:

    fprintf(fd, "          mpfr_add (s, s, tmp, MPFR_RNDN);\n"):

    if (i<nc) then fprintf(fd, "\n          k += %d;\n", init_cond[i+1][1]-i0)
    else  fprintf(fd, "\n          k += %d;\n", d-i0)
    fi:
  od:

  ######################################################
  ################### Error analysis ###################
  ######################################################

  maxofti := 0: # store the maximum of the error counters of the initial conditions
  for i from 1 to nc do
    var := sprintf("tip%d", init_cond[i][1]):
    if find_in_error_counter(var, error_counter) > maxofti
      then maxofti := find_in_error_counter(var, error_counter):
    fi:
  od:
  additional_error := find_in_error_counter("tmp", temp[3]) - error_in_loop:

  ######################################################
  #### Ziv' loop: stopping criterion for the series ####
  ######################################################

  # The first neglected term is tk, so the remainder is made by
  # tk + t(k+d) + t(k+2d).... and the corresponding series
  # beginning with t(k+1), t(k+2), etc. up to t(k+d-1).
  #
  # We have t(k0+d) = c*s1(k0)/s1(k0+d) * s2(k0+d)/s2(k0)* p(k0)/q(k0) * x^d t(k0)
  # (where x=u/v or x=1 in cases of rational series or constant series)
  # So it suffices that:
  # forall k0>=k-d, |c * s1(k0)/s1(k0+d) * s2(k0+d)/s2(k0) * p(k0)/q(k0) * x^d| <= 1/2     (1)
  #
  # If this is true, |tk| = |c*s1(k-d)/s1(k) * s2(k)/s2(k-d)* p(k-d)/q(k-d) * x^d t(k-d)| <= t(k-d)/2
  # This is also true for larger values of k, so we can bound |tk + t(k+d) + t(k+2d) + ...| by |t(k-d)|.
  # And the same holds for |t(k+1) + ...|, |t(k+2) + ...|, etc. up to |t(k+d-1)+...|.
  #
  # global_test depends on k and we must satisfy:
  #  "if (global_test) then (1) holds".
  #
  # the total remainder is bounded by 2*nc*tk.

  fprintf(fd, "          global_test = xxx; /* FIXME: set the value in order to ensure that, whenever global_test is true, we have: forall k'>=k, |r(k')*x^d| <= 1/2, where r is the fraction such that a(n)=r(n)a(n-d)*/\n"):
  printf("The code contains a variable global_test that you must manually set to a suitable value, in order to ensure that when global_test is true, the following holds:\n"):
  printf("        forall k'>=k, |r(k')*x^d| <= 1/2, where r is the fraction such that a(n)=r(n)a(n-d)\n"):
  guard_bits := 1+1+ceil(log[2](nc)):
  for i from 1 to nc do
    i0 := init_cond[i][1]:
    fprintf(fd, "          test%d = ( (!MPFR_IS_ZERO(s))\n", i0):
    fprintf(fd, "                    && ( MPFR_IS_ZERO(tip%d)\n", i0):
    fprintf(fd, "                         || (MPFR_EXP(tip%d) + (mp_exp_t)prec + %d <= MPFR_EXP(s))\n", i0, guard_bits):
    fprintf(fd, "                         )\n"):
    fprintf(fd, "                    );\n"):
  od:
  fprintf(fd, "          if (");
  for i from 1 to nc do
    fprintf(fd, "test%d && ", init_cond[i][1]):
  od:
  fprintf(fd, "global_test)\n"):
  fprintf(fd, "            break;\n"):
  fprintf(fd, "        }\n\n"):

  ######################################################
  ############## Ziv' loop: testing final ##############
  ######################################################

  fprintf(fd, "      MPFR_LOG_MSG ((\"Truncation rank: %%lu\\n\", k));\n\n"):
  fprintf(fd, "      err = ERRORANALYSISK + conditionNumber - MPFR_GET_EXP (s);\n\n"):
  fprintf(fd, "      /* err is the number of bits lost due to the evaluation error */\n"):
  fprintf(fd, "      /* wprec-(prec+1): number of bits lost due to the approximation error */\n"):
  fprintf(fd, "      MPFR_LOG_MSG ((\"Roundoff error: %%Pu\\n\", err));\n"):
  fprintf(fd, "      MPFR_LOG_MSG ((\"Approxim error: %%Pu\\n\", wprec-prec-1));\n\n"):
  fprintf(fd, "      if (wprec < err+1)\n"):
  fprintf(fd, "        correct_bits=0;\n"):
  fprintf(fd, "      else\n"):
  fprintf(fd, "        {\n"):
  fprintf(fd, "          if (wprec < err+prec+1)\n"):
  fprintf(fd, "            correct_bits =  wprec - err - 1;\n"):
  fprintf(fd, "          else\n"):
  fprintf(fd, "            correct_bits = prec;\n"):
  fprintf(fd, "        }\n\n"):
  fprintf(fd, "      if (MPFR_LIKELY (MPFR_CAN_ROUND (s, correct_bits, MPFR_PREC (y), rnd)))\n"):
  fprintf(fd, "        break;\n\n"):

  fprintf(fd, "      if (correct_bits == 0)\n"):
  fprintf(fd, "        {\n"):
  fprintf(fd, "          assumed_exponent *= 2;\n"):
  fprintf(fd, "          MPFR_LOG_MSG ((\"Not a single bit correct (assumed_exponent=%%lu)\\n\",\n"):
  fprintf(fd, "                         assumed_exponent));\n"):
  fprintf(fd, "          wprec = prec + ERRORANALYSISK + conditionNumber + assumed_exponent;\n"):
  fprintf(fd, "        }\n"):
  fprintf(fd, "      else\n"):
  fprintf(fd, "        {\n"):
  fprintf(fd, "          if (correct_bits < prec)\n"):
  fprintf(fd, "            { /* The precision was badly chosen */\n"):
  fprintf(fd, "              MPFR_LOG_MSG ((\"Bad assumption on the exponent of %s(x)\", 0));\n", name):
  fprintf(fd, "              MPFR_LOG_MSG ((\" (E=%%ld)\\n\", (long) MPFR_GET_EXP (s)));\n"):
  fprintf(fd, "              wprec = prec + err + 1;\n"):
  fprintf(fd, "            }\n"):
  fprintf(fd, "          else\n"):
  fprintf(fd, "            { /* We are really in a bad case of the TMD */\n"):
  fprintf(fd, "              MPFR_ZIV_NEXT (loop, prec);\n\n"):

  fprintf(fd, "              /* We update wprec */\n"):
  fprintf(fd, "              /* We assume that K will not be multiplied by more than 4 */\n"):
  fprintf(fd, "              wprec = prec + ERRORANALYSIS4K + conditionNumber\n"):
  fprintf(fd, "                - MPFR_GET_EXP (s);\n"):
  fprintf(fd, "            }\n"):
  fprintf(fd, "        }\n\n"):

  fprintf(fd, "    } /* End of ZIV loop */\n\n"):
  fprintf(fd, "  MPFR_ZIV_FREE (loop);\n\n"):
  fprintf(fd, "  r = mpfr_set (res, s, rnd);\n\n"):

  ######################################################
  ################ Clearing everything #################
  ######################################################

  fprintf(fd, "  mpfr_clear (s);\n"):
  if (hardconstant=1)
  then
    fprintf(fd, "  mpfr_clear (x%d);\n", d):
  fi:
  if (type = FUNCTION_SERIES)
  then
    fprintf(fd, "  mpfr_clear (tmp);\n"):
    for i from 1 to nops(exponents)-1 do
      fprintf(fd, "  mpfr_clear (x%d);\n", exponents[i]):
    od:
  fi:

  for i from 1 to nc do
    fprintf(fd, "  mpfr_clear (tip%d);\n", init_cond[i][1]):
  od:

  fprintf(fd, "\n"):
  fprintf(fd, "  MPFR_SAVE_EXPO_FREE (expo);\n"):
  fprintf(fd, "  return mpfr_check_range (res, r, rnd);\n"):
  fprintf(fd, "}\n"):

  fclose(fd):

  for i from 1 to nops(required_mulsi) do
    printf("You need to provide a mpfr_mul_si%d function.\n", required_mulsi[i]):
    printf("  -> This can be achieved by a call to generate_muldivsin(\"mul\", %d):\n",  required_mulsi[i]):
  od:
  for i from 1 to nops(required_divsi) do
    printf("You need to provide a mpfr_div_si%d function.\n", required_divsi[i]):
    printf("  -> This can be achieved by a call to generate_muldivsin(\"div\", %d):\n",  required_divsi[i]):
  od:

  ######################################################
  ################## Error analysis ####################
  ######################################################

  printf("\n\n"):
  printf("Before the loop, we have "):
  for i from 1 to nc do
    var := sprintf("tip%d", init_cond[i][1]):
    printf("%s {%d}", var, find_in_error_counter(var, error_counter)):
    if (i <> nc) then printf(", ") else printf("\n") fi:
  od:
  printf("Each step of the loop adds another {%d}\n", error_in_loop):
  if (additional_error <> 0)
  then printf("Moreover, the multiplication by %a adds another {%d} to each term before it is summed.\n", subs(n=k, s2/s1), additional_error)
  fi:
  printf("Finally, we have s = sum_(i=0)^(k-1) ( ti{%d + %dk} )\n", maxofti + additional_error + 1 - error_in_loop, error_in_loop):
  printf("We bound it by {(k+%d)*2^(%d)}\n", ceil( (maxofti + additional_error + 1 - error_in_loop)/error_in_loop), ceil(log[2](error_in_loop))):

  a := ceil( (maxofti + additional_error + 1 - error_in_loop)/error_in_loop):
  b := ceil(log[2](error_in_loop)):

  if (a > 0)
    then var := sprintf("MPFR_INT_CEIL_LOG2 (prec + %d)", a)
    elif (a=0) then  var := sprintf("MPFR_INT_CEIL_LOG2 (prec)")
    else sprintf("MPFR_INT_CEIL_LOG2 (prec - %d)", -a)
  fi:
  if (b > 0) then var := sprintf("%s + %d", var, b+2) fi:
  var := sprintf("sed -n -i 's/ERRORANALYSISPREC/%s/g;p' %s", var, filename):
  system(var):

  if (a > 0)
    then var := sprintf("MPFR_INT_CEIL_LOG2 (k + %d)", a)
    elif (a=0) then  var := sprintf("MPFR_INT_CEIL_LOG2 (k)")
    else sprintf("MPFR_INT_CEIL_LOG2 (k - %d)", -a)
  fi:
  if (b > 0) then var := sprintf("%s + %d", var, b+2) fi:
  var := sprintf("sed -n -i 's/ERRORANALYSISK/%s/g;p' %s", var, filename):
  system(var):

  if (a > 0)
    then var := sprintf("MPFR_INT_CEIL_LOG2 (k + %d)", a)
    elif (a=0) then  var := sprintf("MPFR_INT_CEIL_LOG2 (k)")
    else sprintf("MPFR_INT_CEIL_LOG2 (k - %d)", -a)
  fi:
  var := sprintf("%s + %d", var, b+4):
  var := sprintf("sed -n -i 's/ERRORANALYSIS4K/%s/g;p' %s", var, filename):
  system(var):

end proc:


`depends/Bucket` := proc(mr, r::(name=range), x, $)
  depends(rhs(r), x) or depends(mr, x minus {lhs(r)})
end proc:

`depends/Index` := proc(n, o::name, e, mr, x, $)
  depends({n,e}, x) or depends(mr, x minus {o})
end proc:

# note that v _can_ occur in m1.
`depends/Let` := proc(m1, v::name, m2, x, $)
  depends(m1, x) or depends(m2, x minus {v})
end proc:

`eval/Bucket` := proc(e::anyfunc(anything,name=range), eqs, $)
  local bvar, body;
  bvar, body := BindingTools:-generic_evalat(op([2,1],e), op(1,e), eqs);
  eval(op(0,e), eqs)(body, bvar = eval(op([2,2],e), eqs))
end proc:

`eval/Index` := proc(e::anyfunc(anything,name,anything,anything), eqs, $)
  local o, mr;
  o, mr := BindingTools:-generic_evalat(op(2,e), op(4,e), eqs);
  eval(op(0,e), eqs)(eval(op(1,e), eqs), o, eval(op(3,e), eqs), mr)
end proc:

`eval/Let` := proc(e, eqs, $)
  local m1, v, m2;
  m1, v, m2 := op(e);
  eval(op(0,e), eqs)(eval(m1, eqs), BindingTools:-generic_evalat(v, m2, eqs))
end proc:

Summary := module ()
  option package;
  export RoundTrip, Summarize, SummarizeKB, bucket, summarize;
  global Bucket, Fanout, Index, Split, Nop, Add, Let,
         SumIE, ProductIE, BucketIE;
  uses Hakaru, KB;

  RoundTrip := proc(e)
    local result;
    interface(screenwidth=9999, prettyprint=0, warnlevel=0,
      showassumed=0,quiet=true);
    kernelopts(assertlevel=0);
    result := eval(ToInert(Summarize(_passed)), _Inert_ATTRIBUTE=NULL);
    lprint(result)
  end proc;

  Summarize := proc(e, {ctx :: list := []}, $)
    local ie;
    ie := table('[sum    =SumIE    , Sum    =SumIE    ,
                  product=ProductIE, Product=ProductIE,
                  bucket =BucketIE , Bucket =BucketIE ]');
    subsindets(SummarizeKB(e, foldr(assert, empty, op(ctx))),
               And(specfunc({indices(ie, 'nolist')}),
                   anyfunc(anything, anything=range)),
               e -> subsop(0 = ie[op(0,e)],
                           applyop(`+`, [2,2,2], e, 1)))
  end proc;

  SummarizeKB := proc(e, kb :: t_kb, $)
    local patterns, x, kb1, e1, rng, summary, mr, f;
    if not hasfun(e, '{Sum,sum}', 'piecewise') then
      e;
    elif e :: '{known_continuous, known_discrete,
                specfunc({Msum, Weight, Fanout, Split, Nop, Add,
                          exp, log, GAMMA, Beta, idx, And, Or, Not}),
                `+`, `*`, `^`, `..`, boolean, indexed, list}' then
      map(procname, _passed);
    elif e :: 'Context(anything, anything)' then
      kb1 := assert(op(1,e), kb);
      applyop(procname, 2, e, kb1);
    elif e :: 'specfunc(piecewise)' then
      kb_piecewise(e, kb, ((lhs, kb) -> lhs), SummarizeKB);
    elif e :: 'lam(name, anything, anything)' then
      patterns := htype_patterns(op(2,e));
      if patterns :: Branches(Branch(PVar(name),anything)) then
        # Eta-expand the function type
        x := op(1,e);
        x, kb1 := genType(x, op(2,e), kb);
        e1 := eval(op(3,e), op(1,e)=x);
        lam(x, op(2,e), SummarizeKB(e1, kb1))
      else
        # Eta-expand the function type and the sum-of-product argument-type
        x := op(1,e);
        if depends([e,kb], x) then x := gensym(x) end if;
        e1 := eval(op(3,e), op(1,e)=x);
        lam(x, op(2,e), 'case'(x,
          map(proc(branch)
                local eSubst, pSubst, p1, binds, y, kb1, i, pSubst1;
                eSubst, pSubst := pattern_match([x,e], x, op(1,branch));
                p1 := subs(pSubst, op(1,branch));
                binds := [pattern_binds(p1)];
                kb1 := kb;
                pSubst1 := table();
                for i from 1 to nops(binds) do
                  y, kb1 := genType(op(i,binds), op([2,i],branch), kb1);
                  pSubst1[op(i,binds)] := y;
                end do;
                pSubst1 := op(op(pSubst1));
                Branch(subs(pSubst1, p1),
                       SummarizeKB(eval(eval(e1,eSubst),pSubst1), kb1))
              end proc,
              patterns)))
      end if
    elif e :: 'ary(anything, name, anything)' then
      x, kb1 := genType(op(2,e), HInt(closed_bounds(0..op(1,e)-1)), kb);
      e1 := SummarizeKB(eval(op(3,e), op(2,e)=x), kb1);
      subsop(2=x, 3=e1, e);
    elif e :: 'And(specfunc({sum, Sum, product, Product}),
                   anyfunc(anything, name=range))' then
      rng := op([2,2],e);
      x, kb1 := genType(op([2,1],e), HInt(closed_bounds(rng)), kb);
      rng := map(SummarizeKB, rng, kb);
      e1 := eval(op(1,e), op([2,1],e)=x);
      if op(0, e) in '{sum, Sum}' and has(e1, 'piecewise') then
        mr, f := summarize(e1, kb, x, summary);
        if hasfun(mr, '{Fanout, Index}') then
          mr := SummarizeKB(mr, kb1);
          return Let(Bucket(mr, x=rng),
                     summary,
                     NewSLO:-simplify_factor_assuming(f, kb));
        end if;
      end if;
      e1 := SummarizeKB(e1, kb1);
      subsop(2=(x=rng), 1=e1, e);
    else
      error "SummarizeKB doesn't know how to handle %1", e;
    end if;
  end proc;

  bucket := proc(mr, r::(name=range), cond::list:=[], $)
    if mr :: 'Fanout(anything,anything)' then
      Pair(op(map(procname, _passed)))
    elif mr :: 'Split(anything,anything,anything)' then
      Pair(bucket(op(2,mr), r, [    op(1,mr) ,op(cond)]),
           bucket(op(3,mr), r, [Not(op(1,mr)),op(cond)]))
    elif mr :: 'Index(anything,name,anything,anything)' then
      ary(op(1,mr), op(2,mr),
          bucket(op(4,mr), r, [op(2,mr)=op(3,mr),op(cond)]));
    elif mr :: 'Nop()' then
      _Unit
    elif mr :: 'Add(anything)' then
      sum(piecewise_And(cond, op(1,mr), 0), r)
    else
      'procname(_passed)'
    end if;
  end proc;

  summarize := proc(ee,
                    kb :: t_kb,
                    i :: {name,list(name),set(name)},
                    summary, $)
    local e, r, s,
          e1, mr1, f1, e2, mr2, f2,
          variables, var_outerness, outermost, thunk, consider,
          o, t, lo, hi, b, a;

    # Try to ensure termination
    e := simplify_assuming(ee, kb);
    if length(e) >= length(ee) then e := ee end if;

    # Nop rule
    if Testzero(e) then return Nop(), 0 end if;

    r := indets(e, '{relation, logical, specfunc({And,Or})}');
    s, r := selectremove(depends, r, i);
    # Fanout rule
    r := sort(convert(r, 'list'), 'length');
    while nops(r) > 0 do
      e1 := eval(e, r[-1]=true);  if e = e1 then r := r[1..-2]; next end if;
      e2 := eval(e, r[-1]=false); if e = e2 then r := r[1..-2]; next end if;
      mr1, f1 := summarize(e1, kb, i, 'fst(summary)');
      mr2, f2 := summarize(e2, kb, i, 'snd(summary)');
      return Fanout(mr1, mr2), 'piecewise'(r[-1], f1, f2);
    end do;

    variables     := kb_to_variables(kb);
    var_outerness := table(variables =~ [seq(1..nops(variables))]);
    outermost     := -infinity;
    consider      := proc(expr, th, $)
      local outerness;
      outerness := map((n -> var_outerness[n]), indets(expr, 'name'));
      outerness := min(select(type, outerness, 'integer'));
      if outermost < outerness then
        outermost := outerness;
        thunk     := th;
      end if;
    end proc;
    if has(variables, i) then
      error "The loop to be summarized shadows the variable %1", i;
    end if;
    for r in s do
      e1 := eval(e, r=true);  if e = e1 then next end if;
      e2 := eval(e, r=false); if e = e2 then next end if;
      # Index rule
      if r :: `=` and Testzero(e2) then
        for o in indets(r, 'name') minus convert(i, 'set') do
          if not (var_outerness[o] :: integer) then next end if;
          t := getType(kb, o);
          if not (t :: specfunc(HInt)) then next end if;
          lo := op(select(type, t, 'Bound(identical(`>=`), anything)'));
          if lo = NULL then next else lo := op(2,lo) end if;
          hi := op(select(type, t, 'Bound(identical(`<=`), anything)'));
          if hi = NULL then next else hi := op(2,hi) end if;
          if not ispoly(`-`(op(r)), 'linear', o, 'b', 'a') then next end if;
          b := Normalizer(-b/a);
          consider(b, ((e1, o, lo, hi, b) -> proc($)
            local mr, f;
            mr, f := summarize(e1, kb, i, 'idx'(summary, o-lo));
            Index(hi-lo+1, o, b, mr),
              'piecewise'(And(o::integer, lo<=o, o<=hi), f, 0);
          end proc)(e1, o, lo, hi, b));
        end do;
      end if;
      # Split rule
      consider(r, ((e1, e2, r) -> proc($)
        local mr1, f1, mr2, f2;
        mr1, f1 := summarize(e1, kb, i, 'fst(summary)');
        mr2, f2 := summarize(e2, kb, i, 'snd(summary)');
        Split(r, mr1, mr2), f1 + f2;
      end proc)(e1, e2, r));
    end do;
    if thunk :: procedure then
      return thunk();
    else
      # Add rule
      return Add(e), summary;
    end if;
  end proc;
end module; # Summary


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

`is_element/P/itloc` := (N::set(nonnegint)) -> (AA) -> (T) -> proc(TT)
 `is_element/M1/itloc`(N)(AA)(TT) and `omega/itloc`(TT) = T;
end:

`is_leq/P/itloc` := (N::set(nonnegint)) -> (AA) -> (T) -> proc(TT0,TT1)
 `and`(seq(TT0[i] minus TT1[i] = {},i=1..nops(TT0)));
end:

`list_elements/P/itloc` := (N::set(nonnegint)) -> (AA) -> proc(T)
 local BB,L;

 BB := `cap/itloc`(AA,T);
 L := `list_elements/M1/itloc`(T)(BB);
 L := select(TT -> `omega/itloc`(TT) = T,L);
 return L;
end:

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

`is_element/P_star/itloc` := (N) -> (AA) -> (T) -> (iu) -> proc(TT)
 local i,u;

 i,u := op(iu);

 if not(`is_element/P/itloc`(N)(AA)(T)(TT)) then
  return false;
 fi;

 if max(u,op(TT[i])) <> u then
  return false;
 fi;

 return true;
end:

`Alpha/itloc` := (N) -> (AA) -> (T) -> (iu) -> proc(TT)
 local i,u,r,UU;

 i,u := op(iu);

 if max(op(TT[i])) < u then
  return TT;
 fi;

 r := nops(AA);
 UU := [
  seq(TT[k],k=1..i),
  TT[i+1] union {u},
  seq(TT[k],k=i+2..r)
 ];

 return UU;
end:

`Beta/itloc` := (N) -> (AA) -> (T) -> (iu) -> proc(TT)
 local i,u,r,UU;

 i,u := op(iu);

 if max(op(TT[i])) < u then
  return TT;
 fi;

 r := nops(AA);
 UU := [
  seq(TT[k],k=1..i-1),
  TT[i] minus {u},
  TT[i+1] union {u},
  seq(TT[k],k=i+2..r)
 ];

 return UU;
end:

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

`is_element/MM/itloc` := (N) -> (AA) -> (T) -> (i) -> proc(TT)
 local a,r,k,UU;

 if not(`is_element/P/itloc`(N)(AA)(T)(TT)) then
  return false;
 fi;

 a := `bot/threads/itloc`(`cap/itloc`(AA,T));
 r := nops(AA);
 UU := [
  select(t -> t <= a[1],T),
  seq(select(t -> a[k-1] <= t and t <= a[k],T),k=2..r)
 ];

 for k from 1 to i do
  if TT[k] <> UU[k] then return false; fi;
 od;

 return true;
end:

`is_element/NN/itloc` := (N) -> (AA) -> (T) -> (i) -> proc(TT)
 local a,r,k,UU;

 if not(`is_element/P/itloc`(N)(AA)(T)(TT)) then
  return false;
 fi;

 a := `bot/threads/itloc`(`cap/itloc`(AA,T));
 r := nops(AA);
 UU := [
  select(t -> t <= a[1],T),
  seq(select(t -> a[k-1] <= t and t <= a[k],T),k=2..r)
 ];

 for k from 1 to i do
  if TT[k] <> UU[k] then return false; fi;
 od;

 if i = 0 and not(member(min(op(T)),TT[1])) then
  return false;
 fi;

 if i > 0 and not(member(a[i],TT[i+1])) then
  return false;
 fi;

 return true;
end:

`is_element/LL/itloc` := (N) -> (AA) -> (T) -> (iu) -> proc(TT)
 local i,u,a,r,k,UU;

 i,u := op(iu);

 if not(`is_element/NN/itloc`(N)(AA)(T)(i-1)(TT)) then
  return false;
 fi;

 if max(op(TT[i])) > u then
  return false;
 fi;

 return true;
end:

`theta/itloc` := (N) -> (AA) -> (T) -> (i) -> proc(TT)
 local a,r;

 a := `bot/threads/itloc`(`cap/itloc`(AA,T));
 r := nops(AA);

 return [
  seq(TT[k],k=1..i),
  TT[i+1] union {a[i]},
  seq(TT[k],k=i+2..r)
 ];
end;