text:
# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

Declare(rDFTSkew, realDFTPerm, fRealAccPease, _rDFT_u_func);

# A function to calculate "next" values of u for iteration j (used in rDFTSkew C-T rule)
_rDFT_u_func := function(u, j, k)
    if (u=0) then
        return V(fdiv(j, (2*k)));
    else
        if (IsEvenInt(j)) then
            return fdiv((u + idiv(j,2)), k);
        else
            return fdiv((1 - u + idiv(j,2)), k);
        fi;
    fi;
end;

# Recusively determine the value for u given k, q, and l, where
#    k = radix/2,
#    q = log_k(m),
#    l = access function (or iterator).
_rDFT_u_rec := function(depth, u, k, q, l)
   if (depth = q) then
       return V(u);
   else
       return _rDFT_u_func(_rDFT_u_rec(depth+1, u, k, q, l), idiv(imod(l, k^(depth+1)), V(k^(depth))), k);
   fi;
end;

# An Exp wrapper for _rDFT_u_rec.  This is used so Spiral does not try to evaluate the expression
# until Process_fPrecompute is called.
Class(_rDFT_u_rec_exp, Exp, rec(
    ev := self >> let(
        depth := self.args[1].ev(),
        u := self.args[2].ev(),
        k := self.args[3].ev(),
        q := self.args[4].ev(),
        l := self.args[5].ev(),
        _rDFT_u_rec(depth, u, k, q, l).ev()
    ),

    computeType := self >> TReal
));

# A function wrapper for _rDFT_u_rec_exp.  The rewriting rule uses this function.  Then, it is evaluated to the
# Exp above.  Then, that will evetually be evaluated into the final value.
Class(_rDFT_u_rec_wrapper, FuncClass, rec(
    def := (d, u, k, q, l) -> rec(N := 1, n := 1),
    lambda := self >> let(i := Ind(1), Lambda(i, _rDFT_u_rec_exp(self.params[1], self.params[2], self.params[3], self.params[4], self.params[5]))),
    t := TReal,
    eval := self >> When(self.domain() = 1 and self.rank() = 0, self.at(0).eval(), self),
    ev := self >> Checked(self.domain() = 1, self.at(0).ev()),
));

# An Exp wrapper for _rDFT_u_func.  This is used in the C-T rule, so the "next" values of u can be
# calcualted (but not evaluated until they can be.)
Class(_rDFT_u_r_exp, Exp, rec(
   ev := self >> let(
       u := self.args[1].ev(),
       j := self.args[2].ev(),
       k := self.args[3].ev(),
       _rDFT_u_func(u, j, k).ev()
   ),

   computeType := self >> TReal
));

# rDFTSkew(n,u).  This is the transform our streaming and Pease rules apply to.
Class(rDFTSkew, TaggedNonTerminal, rec(
    abbrevs := [(n,u) -> Checked(IsPosInt(n), [n, u])],
    dims := self >> let(n := self.params[1], [n,n]),
    isReal := self >> true,

    terminate := self >> let(
        n := self.params[1],
        k := 2,
        m := n/(2*k),
        a := Cond(IsValue(self.params[2]), self.params[2], V(self.params[2])),
        Cond(n=4,

            Cond(a=0,
                Tensor(F(2), I(2)),
                Diag(1,1,1,-1) * Tensor(F(2), I(2)) *
                   DirectSum(I(2), Mat([[cospi(a.ev()), -1*sinpi(a.ev())], [sinpi(a.ev()), cospi(a.ev())]])) *
                   L(4,2)
            ),

            RC(Cond(a=0, Kp(k*m, m), K(k*m, m))) *
            DirectSum(
                rDFTSkew(2*m, _rDFT_u_func(a, 0, k)).terminate(),
                rDFTSkew(2*m, _rDFT_u_func(a, 1, k)).terminate()
            ) *
            Tensor(rDFTSkew(2*k, a), I(m))
        )
    ),
));

NewRulesFor(PkRDFT1, rec(

    # PkRDFT1(n,0) --> DirectSum(F(2), I(n-2)) * rDFTSkew(n,0)
    # We simplify by writing the direct sum already in TTensorInd form.

    PkRDFT1_rDFTSkew := rec(
        forTransposition := false,
        applicable := (self, nt) >> nt.params[2] = 1,  #! temporary hack
        children := (self, t) >> let(
            n := t.params[1],
            i := Ind(n/2),
            tags := t.getTags(),

            [[ TCompose([TTensorInd(COND(eq(i,0), F(2), I(2)), i, APar, APar), rDFTSkew(n,V(0))]).withTags(tags) ]]
        ),

        apply := (nt, c, cnt) -> c[1]
    ),

    PkRDFT1_DFT := rec(
        forTransposition := false,
        applicable := (self, nt) >> nt.params[2] = 1, #! temporary
        children := (self, t) >>
            [[TCompose([TConjEven(t.params[1]), TRC(DFT(t.params[1]/2))]).withTags(t.getTags())]],

        apply := (nt, c, cnt) -> c[1]
    )
));

NewRulesFor(rDFTSkew, rec(
    # opts.breakdownRules := rec(rDFTSkew := [CopyFields(rDFT_Skew_CT, rec(radix:=4))])
    #

    # rDFTSkew(2*k*m,0) --> Perm * (I_k x rDFTSkew(2*m, f(u,l))) * (rDFTSkew(2*k, u) x I_m)
    rDFT_Skew_CT := rec(
        forTransposition := false,
        applicable := (self, nt) >> nt.params[1] > 4 and not nt.isTag(1, AStream),
        children := (self, t) >> let(
            n := t.params[1],
            m := 2,
            k := n/(2*m),

            u := t.params[2],
            j := Ind(k),

            [[ rDFTSkew(2*m, _rDFT_u_r_exp(u, j, k)), rDFTSkew(2*k, u), InfoNt(j) ]]
         ),

        apply := (t,c,nt) -> let(
            n := t.params[1],
            m := 2,
            k := n/(2*m),
            u := t.params[2],
            j := nt[3].params[1],
            D := Dat1d(TReal, 1),

            Data(D, fPrecompute(FList(TReal, [cospi(u)])),
                COND(eq(nth(D,0),1), Tensor(Kp(k*m,m), I(2)), Tensor(K(k*m,m), I(2))) *
                IDirSum(j, k, c[1]) *
                Tensor(c[2], I(m))
            )

        )

    ),

    # rDFTSkew(4,u) base rule.
    rDFT_Skew_Base4 := rec(
        forTransposition := false,
        applicable := (self, nt) >> nt.params[1] = 4,
        apply := (t, c, nt) -> let(
            u := t.params[2],
            DC := Dat1d(TReal, 1),
            DS := Dat1d(TReal, 1),

            Data(DC, fPrecompute(FList(TReal, [cospi(u)])),
                Data(DS, fPrecompute(FList(TReal, [sinpi(u)])),
                    COND(eq(nth(DC,0),1), I(4), Diag(1,1,1,-1)) *
                    Tensor(F(2), I(2)) *
                    DirectSum(I(2), Mat([[nth(DC,0), -1*nth(DS,0)], [nth(DS,0), nth(DC,0)]])) *
                    COND(eq(nth(DC,0),1), I(4), L(4,2))
                )

            )
        )
    ),

    # Streaming rDFTSkew(n,u) rule.
    rDFT_Skew_Stream := rec(
        forTransposition := false,

        radix := 4,

        applicable := (self, nt) >> let(
            k := self.radix/2,
            m := nt.params[1]/(2*k),
            logM := LogInt(m,k),
            IsInt(nt.params[1]/(2*k)) and nt.isTag(1, AStream) and nt.getTags()[1].bs >= self.radix and
            (k ^ logM = m)
        ),

        freedoms := t -> [[ ]],

        children := (self, nt) >> let(
            P    := nt.params,
            k    := self.radix/2,
            m    := P[1]/(k*2),
            tags := nt.getTags(),
            q    := LogInt(m,k),
            u    := nt.params[2],

            itvars := List([0..q], i->Ind(m)),

            [[ TCompose(Concatenation(
                 [ TPrm(realDFTPerm(2*k*m, k, u)) ],
                 [ TTensorInd(rDFTSkew(2*k, _rDFT_u_rec_wrapper(0, u, k, q, itvars[1])), itvars[1], APar, APar) ],
                 List([1..q], i-> TCompose([
                     Cond(i=1,
                         TTensorI(TPrm(L(2*k^(i+1), 2*k)), m/(k^i), APar, APar),
                         TTensorI(TPrm(Compose(Tensor(I(k), L(2*(k^i), k^(i-1))), L(2*k^(i+1), 2*k))), m/(k^i), APar, APar)
                     ),
                     TTensorInd(rDFTSkew(2*k, _rDFT_u_rec_wrapper(0, u, k, q-i, bin_shr(itvars[i+1], Log2Int(k^i)))),
                         itvars[i+1], APar, APar)

                 ])),
                 [ TPrm(L(2*k^(q+1), m)) ]
             )).withTags(tags)]]
        ),

        apply := (nt, c, cnt) -> c[1]

    ),

    # Non-uniform radix streaming breakdown rule for rDFTSkew(n,u)
    # This does not work correctly when u<>0 and k > 2.   I still need to debug this.
    # NOTE
    rDFT_Skew_Stream_Mult_Radices := rec(
        forTransposition := false,

        applicable := (self, nt) >> let(
            n  := nt.params[1],
            rp := paradigms.stream.rDFT_Skew_Stream.radix,
            kp := rp/2,
            mp := kp ^ LogInt(n/(2*kp), kp),
            m  := kp * mp,
            k  := n/(2*m),
            u := nt.params[2],

            IsInt(nt.params[1]/(2*k)) and nt.isTag(1, AStream) and
                not paradigms.stream.rDFT_Skew_Stream.applicable(nt) and (u=0 or k<=2)
        ),

        freedoms := t -> [[ ]],

        children := (self, nt) >> let(
            n  := nt.params[1],
            rp := paradigms.stream.rDFT_Skew_Stream.radix,
            kp := rp/2,
            mp := kp ^ LogInt(n/(2*kp), kp),
            m  := kp * mp,
            k  := n/(2*m),
            logM := LogInt(m,k),

            tags := nt.getTags(),
            u := nt.params[2],
            i := Ind(k),

            [[ TCompose([ TPrm(Tensor(COND(eq(u,0), Kp(k*m, m), K(k*m,m)), I(2))),
                          TTensorInd(rDFTSkew(2*m, _rDFT_u_r_exp(u, i, k)), i, APar, APar),
                          TPrm(L(2*k*m, 2*k)),
                          TTensorI(rDFTSkew(2*k, u), m, APar, APar),
                          TPrm(L(2*k*m, m)) ]).withTags(tags) ]]

        ),

        apply := (nt, c, cnt) -> c[1]

    ),

    # Pease rDFTSkew(n,0) rule
    rDFT_Skew_Pease := rec(
        forTransposition := false,

        unroll_its := 1,
        radix      := 4,

        # For some k between minRadix/2 and maxRadix/2, P[1]=2*k*m, where m=k^p where p is an integer
        applicable := (self, nt) >> let(
            k := self.radix/2,
            m := nt.params[1]/(2*k),
            logM := LogInt(m, k),

            nt.params[2] = 0 and   # If we were to support u <> 0, we would need to change the
                                   # the way we store constants.  The trick we currently use
                                   # to go from O(n log n) to O(n) would not work without modification
                                   # (it may in fact not work at all).
            IsInt(nt.params[1]/(2*k)) and
            (k ^ logM = m) and
            IsInt((logM+1)/self.unroll_its) and
            (logM+1)/self.unroll_its > 1 and
            nt.isTag(1, AStream)
        ),

        freedoms := t -> [[ ]],

	    children := (self, nt) >> let(
            P     := nt.params,
            k     := self.radix/2,
            m     := P[1]/(k*2),
            tags  := nt.getTags(),
            q     := LogInt(m,k),
            it_i  := Ind((q+1) / self.unroll_its),
            it_l  := Ind(m),
            u     := 0,

            it_ur := i >> self.unroll_its * it_i + i,
            f_ur  := i >> fComputeOnline(fRealAccPease(k, m, it_ur(i), it_l)),
            u_ur  := i >> _rDFT_u_rec_wrapper(0, u, k, q, f_ur(i)),

            # This provides stage i of the datapath.
            stage      := i >> TCompose([TTensorInd(rDFTSkew(2*k, u_ur(i)), it_l, APar, APar), TL(2*k*m, 2*m, 1, 1)]),

            # This gives a full stage (multiple stage(i) when we unroll).
            full_stage := List([1..self.unroll_its], t -> stage(t-1)),

            [[ TCompose([
                  realDFTPerm(2*k*m, k, u),
                  TICompose(it_i, it_i.range, TCompose(full_stage)),
                  TL(2*k*m, k*m, 1, 1)
               ]).withTags(tags)]]
        ),

        apply := (nt, c, cnt) -> c[1]
    ),
));

# Pease rDFTSkew access function for rDFTSkew(2*k*m, 0) where i is the stage and l is the position in stage.
#     fRealAccPease(k, m, i, l)
Class(fRealAccPease, FuncClass, rec(
   def := (k, m, i, l) -> rec(N:=m, n:=1),

   domain := self >> 1,
   range  := self >> self.params[2],

   lambda := self >> let(k := self.params[1],
                         m := self.params[2],
                         i := self.params[3],
			             l := self.params[4],
			             j := Ind(1),
                         logk := LogInt(k,2),

       Lambda(j, bin_and(l, bin_shr(m-1, logk*i)))),

   computeType := self >> TInt,
   t := TInt,
   eval := self >> When(self.domain() = 1 and self.rank() = 0, self.at(0).eval(), self),
   ev := self >> Checked(self.domain() = 1, self.at(0).ev())

));

# The output permutation in Pease or streaming rDFTSkew algorithms
# realDFTPerm(n, k), where n = 2*k*m
Class(realDFTPerm, TaggedNonTerminal, rec(

    abbrevs := [(n, k, u)      -> [n, k, u]],

    dims := self >> [ self.params[1], self.params[1] ],
    domain := self >> self.params[1],
    range := self >> self.params[1],

    terminate := self >> let(
	    n := self.params[1],
        k := self.params[2],
        u := Cond(IsExp(self.params[3]), self.params[3].ev(), self.params[3]),
        m := n/(2*k),
        r := LogInt(m,k),

        res := Cond(n < 8, I(n),
            Cond(u=0,
	            Tensor(
                    Compose(List([0..r-1], l->
                        DirectSum(Kp(k*m/(k^l), k*m/(k^(l+1))),
                            Tensor(I(k^l-1), K(k*m/(k^l), k*m/(k^(l+1))))
                        )
                    )),
                    I(2)
                ),
	            Tensor(
                    Compose(List([0..r-1], l->
                        Tensor(I(k^l), K(k*m/(k^l), k*m/(k^(l+1))))
                    )),
                    I(2)
                )
            )
        ),

        Cond(self.transposed, res.transpose(), res)
    ),

    isReal := self >> true,

    print := meth(self, indent, indentStep)
        local lparams, mparams;
        if not IsBound(self.params) then Print(self.name); return; fi;
        Print(self.name, "(");
        if IsList(self.params) then
            lparams := Filtered(self.params, i->not (IsList(i) and i=[]));
            mparams := Filtered(lparams, i->not (IsBool(i) and not i));
            DoForAllButLast(mparams, x -> Print(x, ", "));
            Print(Last(mparams));
        else
            Print(self.params);
        fi;
    Print(")", When(self.transposed, ".transpose()", ""));
    end,

));

NewRulesFor(realDFTPerm, rec(

    # Rule to place realDFTPerm into a StreamPermGen wrapper.
    # realDFTPerm --> StreamPermGen(realDFTPerm)

    realDFTPerm_Stream := rec(
        isApplicable := (self, P) >> Checked(IsList(P[2]) and IsBound(P[2][1].bs),
	                                      IsInt((2^P[1])/P[2][1].bs)),

      children := (self, nt) >> [[]],
      apply := (nt, c, cnt) -> StreamPerm([realDFTPerm(nt.params[1], nt.params[2], nt.params[3])], 1, nt.getTags()[1].bs, 0),
   )
));

Class(TConjEvenStreamBB, NonTerminal, rec(
    abbrevs := [(n,j) -> Checked(IsPosInt(n), [n,j])],
    dims    := self >> [4,4],
    isReal  := self >> true,

    terminate := self >> let(
        j  := self.params[2].ev(),
        n  := self.params[1],

        c  := cospi(2*j/n)/2,
        s  := sinpi(2*j/n)/2,

        Cond(j=0,
            Mat([[1,1,0,0], [1, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),

            Diag(1,1,1,-1) *
            Tensor(F(2), I(2)) *
            DirectSum(Diag(1/2, 1/2), Mat([[c, -1*s], [s, c]])) *
            L(4,2) *
            Mat([[1,0,1,0],[0,1,0,1],[0,1,0,-1],[-1,0,1,0]])

        )
    )
));

NewRulesFor(TConjEven, rec(
    TConjEven_stream := rec(
        switch := false,
        applicable := (self, t) >> 2^Log2Int(t.params[1]) = t.params[1] and
                                       t.isTag(1, AStream) and t.getTags()[1].bs >= 4,

        children := (self, nt) >> let(
            n    := nt.params[1],
            tags := nt.getTags(),
            i    := Ind(n/4),
	    rot  := nt.params[2],

            [[ TCompose([
#                    TPrm(Tensor(DirectSum(I(n/4+1), J(n/4-1)) * L(n/2,2), I(2))),
#                    TPrm(Tensor(Kp(n/2, 2), I(2))),
                    TRC(TPrm(Kp(n/2, 2))),
		    # YSV: NOTE -- handle <rot> below
                    TTensorInd(TConjEvenStreamBB(n, i), i, APar, APar),
#                    TPrm(Tensor(L(n/2, n/4) * DirectSum(I(n/4+1), J(n/4-1)), I(2)))
#                    TPrm(Tensor(Kp(n/2, 2).transpose(), I(2)))
                    TRC(TPrm(Kp(n/2, 2).transpose()))
               ]).withTags(tags)]]
        ),

        apply := (nt, c, cnt) -> c[1]

    )
));

NewRulesFor(TConjEvenStreamBB, rec(
    TConjEvenStreamBB_base := rec(
        switch := false,
        forTransposition := false,
        applicable := (self, nt) >> IsPosInt(nt.params[1]),

        # new: better alg.  store n/2 words, perform 4 multiplications
        apply := (t, c, nt) -> let(
            j := t.params[2],
            n := t.params[1],
            cs := Dat1d(TReal, 1),
            sn := Dat1d(TReal, 1),

            Data(cs, fPrecompute(FList(TReal, [fdiv(cospi(fdiv(2*j,n)),2)])),
                Data(sn, fPrecompute(FList(TReal, [fdiv(sinpi(fdiv(2*j,n)),2)])),
                    COND(eq(j,0),
                        Mat([[1, 1, 0, 0], [1, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),
                        Diag(1,1,1,-1) *
                        Tensor(F(2), I(2)) *
                        Mat([[1/2, 0, 0, 0], [0, 1/2, 0, 0], [0, 0, nth(cs,0), -1*nth(sn,0)], [0, 0, nth(sn,0), nth(cs,0)]]) *
                        L(4,2) *
                        Mat([[1, 0, 1, 0], [0, 1, 0, 1], [0, 1, 0, -1], [-1, 0, 1, 0]])
                    )
                )
            )
        )

        # Store n/2 words
#         apply := (t, c, nt) -> let(
#             j := t.params[2],
#             n := t.params[1],
#             d0 := Dat1d(TReal, 1),
#             d1 := Dat1d(TReal, 1),

#             Data(d0, fPrecompute(FList(TReal, [fdiv(sinpi(fdiv(2*j,n)),2)])),
#                 Data(d1, fPrecompute(FList(TReal, [fdiv((cospi(fdiv(2*j,n))),2)])),
#                     COND(eq(j,0),
#                         Mat([[1, 1, 0, 0], [1, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),
#                         let(
#                             b0 := 0.5+nth(d0,0),
#                             b2 := 0.5-nth(d0,0),
#                             Mat([[b0, nth(d1,0), b2, nth(d1,0)],
#                                  [-1*nth(d1, 0), b0, nth(d1,0), -1*b2],
#                                  [b2, -1*nth(d1,0), b0, -1*nth(d1,0)],
#                                  [-1*nth(d1, 0), -1*b2, nth(d1,0), b0]])
#                         )
#                     )
#             ))
#         )

        # Store 3n/4 words
#         apply := (t, c, nt) -> let(
#             j := t.params[2],
#             n := t.params[1],
#             d0 := Dat1d(TReal, 1),
#             d1 := Dat1d(TReal, 1),
#             d2 := Dat1d(TReal, 1),

#             Data(d0, fPrecompute(FList(TReal, [fdiv((1+sinpi(fdiv(2*j,n))),2)])),
#                 Data(d1, fPrecompute(FList(TReal, [fdiv((cospi(fdiv(2*j,n))),2)])),
#                     Data(d2, fPrecompute(FList(TReal, [fdiv((1-sinpi(fdiv(2*j,n))),2)])),
#                         COND(eq(j,0),
#                             Mat([[1, 1, 0, 0], [1, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),
#                             Mat([[nth(d0, 0), nth(d1,0), nth(d2,0), nth(d1,0)],
#                                  [-1*nth(d1, 0), nth(d0,0), nth(d1,0), -1*nth(d2,0)],
#                                  [nth(d2, 0), -1*nth(d1,0), nth(d0,0), -1*nth(d1,0)],
#                                  [-1*nth(d1, 0), -1*nth(d2,0), nth(d1,0), nth(d0,0)]])
#                         )
#             )))
#         )

     )
));


text:
run := function()
  local a, b, f;
  f := InputTextUser();
  Print("a =\n");
  a := Int(Chomp(ReadLine(f)));
  Print("b =\n");
  b := Int(Chomp(ReadLine(f)));
  Display(Concatenation(String(a), " + ", String(b), " = ", String(a + b)));
  Display(Concatenation(String(a), " - ", String(b), " = ", String(a - b)));
  Display(Concatenation(String(a), " * ", String(b), " = ", String(a * b)));
  Display(Concatenation(String(a), " / ", String(b), " = ", String(QuoInt(a, b)))); # toward 0
  Display(Concatenation(String(a), " mod ", String(b), " = ", String(RemInt(a, b)))); # nonnegative
  Display(Concatenation(String(a), " ^ ", String(b), " = ", String(a ^ b)));
  CloseStream(f);
end;

ext: gap

max_issues_repo_name: seanwallawalla-forks/RosettaCodeData

max_forks_repo_name: seanwallawalla-forks/RosettaCodeData


text:
# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

#
# example:
#
#   d := DataInd(T_Int(8), [0..1]);
#   SMAP(Lambda([d], (2.0 * imod(d, 2) - 1.0)))
#

Class(SMAP, SumsBase, Sym, rec(
    abbrevs := [
        (lambda)  -> Checked(IsLambda(lambda) and Length(Set(List(lambda.vars, e->e.t)))=1, [lambda])
    ],
    def := (lambda) -> Gath(fBase(Length(lambda.vars), 0)),
    dmn := self >> [ TArray(self.params[1].vars[1].t, Length(self.params[1].vars)) ],
    rng := self >> [ TArray(self.params[1].range(),   1) ],
    at  := (arg) >> arg[1].params[1].at(StripList(Drop(arg, 1))),
));

ext: gi


text:
# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

#
IDFUNC := (N,n,ofs) -> #Cond(
#    (N mod n) = 0 and (ofs mod n) = 0 and n<>1, fTensor(fBase(N/n, ofs/n), fId(n)),
#    IsInt(n) and IsInt(N) and IsInt(ofs) and Gcd(n, N, ofs) > 1, let(g := Gcd(n,N, ofs), fTensor(fAdd(N/g, n/g, ofs/g), fId(g))),
    H(N,n,ofs,1);
#);
#    fAdd(N,n,ofs));

RID := (N, n, ofs) -> Gath(IDFUNC(N,n,ofs));
WID := (N, n, ofs) -> Scat(IDFUNC(N,n,ofs));

###########################################################################################
# SumsSPL(spl, opts)
SumsSPL := function(spl, opts)
    local s;
    if IsBound(opts.sumsgen) then
        trace_log.beginStage("SPL","Sigma-SPL", spl);
        s := opts.sumsgen(spl, opts);
        trace_log.endStage("SPL","Sigma-SPL", s);
        return s;
    else
        Error("opts.sumsgen must be defined!");
     fi;
end;

SumsSPLOpts := function(spl,opts)
  local sums;
  sums:=SumsSPL(spl,opts);
  return ApplyStrategy(sums,opts.formulaStrategies.postProcess,UntilDone, opts);
end;

LiftDataSums := function(sums)
    local p, datas, sums;
    p := Pull(sums, Data, e->e.child(1), e->[e.var, e.value]);
    sums := p[2];
    datas := tab();
    DoForAll(p[1], function(vv) datas.(vv[1].id) := vv[2]; end);
    sums.datas := datas;
    return sums;
end;

FinalizeSums := s->s;
sums -> SubstBottomUp(
    sums,
    [Compose, @(1).target(Scat), @(2).target(Gath)],
    e -> let(n := Rows(@(2).val),
         j := Ind(n),
         fbase := fBase(n,j),
         ISum(j, n,
         Scat(fCompose(@(1).val.func, fbase)) *
         Gath(fCompose(@(2).val.func, fbase)))));

###########################################################################################
DoNotReorder := function(sums) sums.doNotReorder := true; return sums; end;
###########################################################################################
NonTerminal.sums := self >> self;

O.sums := self >> self;

PermClass.sums := self >> Prm(self);

Rot.sums := self >> ExpandRotationsSPL(self).sums();

Sym.sums := self >> #When(self.isPermutation(),
#    SumsSPL(Perm(foldPerm(self.getObj()), Rows(self))),
    self.getObj().sums();

I.sums := self >> Prm(fId(self.params[1]));

# Perm is given by a list of numbers, and is only used for unrolled code,
# so I convert it to Gath, which is simpler. Using Prm would require
# something invertible, and a list of numbers is not invertible when
# using FList (inversion not implemented).
#
Perm.sums := self >> let(
    N := self.size,
    plist_r := Permuted([1..N], self.element^-1) - 1, # we want 0-based perms
    #plist_w := Permuted([1..N], self.element) - 1, # we want 0-based perms
    Gath(FList(N, plist_r).setRange(N))
);

Diag._sums := self >> let(
    N  :=  Rows(self),  ind  :=  Ind(N),  d  :=  Dat(TUnknown),
    DoNotReorder(
    ISum(ind, N,
        Data(d, self.element.lambda(),
        Compose(Scat(fBase(N, ind)),
                Blk1( nth(d, ind) ),     # SS( $.. $..)
                Gath(fBase(N, ind)))) ))
);

Diag.sums := self >> self;
RCDiag.sums := self >> self;

Mat.sums := self >> Blk(self.element);

Sparse.sums := self >> Blk(MatSparseSPL(self));

# Operators

Scale.sums := self >> Inherit(self, rec(_children := [self.child(1).sums()]));

#Compose(
#    Diag(LambdaList([1..Rows(self)], Subst(i -> $(self.scalar)))), self.child(1)).sums();

Conjugate.sums := self >> let(
    O := self.child(1), perm := self.child(2), Tperm := perm.transpose(),
    Compose(Tperm, O, perm).sums()
);

Compose.sums := self >> Cond(self.isPermutation(),
    Prm(ApplyFunc(fCompose, List(Reversed(self.children()), c->c.sums().func))),
    Compose(Map(self.children(), c->c.sums())));

_directSumSums := (obj, roverlap, coverlap) ->
    let(cspans  :=  BaseOverlap.spans(roverlap, List(obj.children(), Cols)),
    rspans  :=  BaseOverlap.spans(coverlap, List(obj.children(), Rows)),
    nblocks  :=  obj.numChildren(),
    C  :=  Cols(obj),
    R  :=  Rows(obj),
    OP  :=  When(coverlap > 0, SUMAcc, SUM),
    OP( Map([1..nblocks],
        i -> Compose( #W(N, bksize_cols, fTensor(fBase(nblocks, ind), fId(bksize)))
                      WID(R, 1 + rspans[i][2] - rspans[i][1], rspans[i][1]-1),
                      obj.child(i).sums(),
                  RID(C, 1 + cspans[i][2] - cspans[i][1], cspans[i][1]-1) ))));

DirectSum.sums    := self >> Cond(self.isPermutation(),
    Prm(ApplyFunc(fDirsum, List(self.children(), c->c.sums().func))),
    _directSumSums(self, 0, 0));

ColDirectSum.sums := self >> _directSumSums(self, 0,            self.overlap);

RowTensor.sums := self >>
    let( A := self.child(1),
     i := Ind(self.isize),
     ISum(i, i.range,
         Scat(fTensor(fBase(self.isize, i), fId(Rows(A)))) *
         A.sums() *
         Gath(fAdd(Cols(self), Cols(A), i * (Cols(A) - self.overlap)))) );

ColTensor.sums := self >>
    let( A := self.child(1),
     i := Ind(self.isize),
     ISumAcc(i, i.range,
         Scat(fAdd(Rows(self), Rows(A), i * (Rows(A) - self.overlap))) *
         A.sums() *
         Gath(fTensor(fBase(self.isize, i), fId(Cols(A))))) );

#Tensor.sums := self >>
#    Cond(self.isPermutation(),
#       Prm(
#       ApplyFunc(fTensor, List(self.children(), c->c.sums().func))),

#    self.numChildren() > 2,
#       self.rightBinary().sums(),

#    not IsIdentitySPL(self.child(1)),
#       self.parallelForm().sums(),

#    let(nblocks := Cols(self.child(1)),
#        ind := Ind(nblocks),
#        bkcols := Cols(self.child(2)),
#        bkrows := Rows(self.child(2)),
#        cols := Cols(self), rows := Rows(self),
#        ISum(ind, nblocks,
#        Compose(Scat(fTensor(fBase(nblocks, ind), fId(bkrows))),
#                self.child(2).sums(),
#            Gath(fTensor(fBase(nblocks, ind), fId(bkcols)))))
#    ));

Tensor.gathTensor := (self,i) >> fTensor;
Tensor.scatTensor := (self,i) >> fTensor;
Tensor.fTensor := fTensor;
Tensor.sums := meth(self)
    local ch, col_prods, row_prods, col_prods_rev, row_prods_rev, i, j1, j2, j1b, j2b, bkcols, bkrows, prod, term;
    if self.isPermutation() then return
    Prm(ApplyFunc(self.fTensor, List(self.children(), c->c.sums().func)));
    elif ForAll(self.children(), x->ObjId(x)=Diag) then return
    Diag(ApplyFunc(diagTensor, List(self.children(), c->c.element)));
    fi;

    ch := self.children();
    col_prods := ScanL(ch, (x,y)->x*Cols(y), 1);
    col_prods_rev := Drop(ScanR(ch, (x,y)->x*Cols(y), 1), 1);
    #row_prods := ScanL(ch, (x,y)->x*Rows(y), 1);
    #row_prods_rev := DropLast(ScanR(ch, (x,y)->x*Rows(y), 1), 1);

    prod := [];
    for i in [1..Length(ch)] do
        if not IsIdentitySPL(ch[i]) then
        bkcols := Cols(ch[i]);
        bkrows := Rows(ch[i]);
        j1 := Ind(col_prods[i]);
        j2 := Ind(col_prods_rev[i]);
        j1b := When(j1.range = 1, 0, fBase(j1));
        j2b := When(j2.range = 1, 0, fBase(j2));

        term := Scat(self.scatTensor(i)(Filtered(
            [j1b, fId(bkrows), j2b], x->x<>0))) *
                ch[i].sums() *
            Gath(self.gathTensor(i)(Filtered(
            [j1b, fId(bkcols), j2b], x->x<>0)));

        if j2.range <> 1 then
        term := ISum(j2, j2.range, term); fi;
        if j1.range <> 1 then
        term := ISum(j1, j1.range, term); fi;

        Add(prod, term);
    fi;
    od;
    return Compose(prod);
end;

Class(GammaTensor, Tensor, rec(
    gathTensor := (self,i) >> When(i=self.numChildren(), gammaTensor, fTensor),
    scatTensor := (self,i) >> When(i=1, gammaTensor, fTensor),
    #fTensor := gammaTensor,
    toAMat := self >> self.sums().toAMat()
));

_partSum := l -> List([1..Length(l)+1], i -> Sum(l{[1..i-1]}));

#-----------------------------------------------------------------------
IterDirectSum.sums := self >>
    let(s := fTensor(fBase(self.domain, self.var), fId(Rows(self.child(1)))),
        g := fTensor(fBase(self.domain, self.var), fId(Cols(self.child(1)))),
        ISum(self.var, self.domain,
             Scat(s) * self.child(1).sums() * Gath(g)));

_sbs := function(s, e)
    local cs;
    cs := Set(Copy(s));
    SubtractSet(cs, Set([e]));
    return cs;
end;

#-----------------------------------------------------------------------
IRowDirSum.sums := meth(self)
    local idx, i, ii, bkrows2, ivars,k, nblocks, q, overlap, s, cd1, rows, bkrows, vars, lambda, bs, ps, fps, gcd, rbase, len, rdom, rows_data, rows_by_gcd, rows_by_one, cols, bkcols, cdom, cbase, g,
        j, pj, rlambda;

    nblocks := self.domain;
    q := self.var;
    overlap := self.overlap;
    cd1 := spiral.code.RulesMergedStrengthReduce(self.child(1).dims()[1]);
    vars := _sbs(Filtered(cd1.free(), IsLoopIndex), q);
    if IsInt(cd1) then
        rows := Rows(self);
        bkrows := Rows(self.child(1));
        s := fTensor(fBase(nblocks, self.var), fId(bkrows));
#   FF: NOTE!! temporary hack for SAR
    elif IsExp(cd1) and Length(vars) = 0 then
        bkrows := _evInt(List(self.unrolledChildren(), i->spiral.code.RulesStrengthReduce(Rows(i))));
        rows := Rows(self);
        ps := _partSum(bkrows);
        gcd := Gcd(List(Concat(ps, [rows]), _unwrapV));
        rbase := FData(List(ps, i->TInt.value(i/gcd)));
        rdom := rbase.at(q+1)-rbase.at(q);
        s := fTensor(fAdd(rows/gcd, rdom, rbase.at(q)), fId(gcd));
    elif IsExp(cd1) and ObjId(cd1)=mul and IsValue(cd1.args[1]) then
        if Length(vars)=1 then
            lambda := Lambda(vars, Lambda(q, cd1));
            bs := Product(List(vars, i->i.range));
            bkrows := List(lambda.tolist(), i->i.tolist());
            ps := List(bkrows, _partSum);
            fps := Flat(ps);
            gcd := Gcd(List(Concat(fps, [cd1.args[1].v]), _unwrapV));
            rbase := FData(List(Flat(ps), i->TInt.value(i/gcd)));
            len := Length(ps[1]);
            rdom := rbase.at(vars[1]*len+q+1)-rbase.at(vars[1]*len+q);
            rows_data := idiv(Rows(self), gcd);
            rows_by_gcd := FData(Lambda(vars, rows_data).tolist()).at(vars[1]);
            s := fTensor(fAdd(rows_by_gcd, rdom, rbase.at(vars[1]*len+q)), fId(gcd));
        elif Length(vars) = 2 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
            pj := Filtered(vars, IsParallelLoopIndex)[1];
            j := Filtered(vars, i -> not IsParallelLoopIndex(i))[1];
            lambda := Lambda(pj, Lambda(j, Lambda(q, cd1)));
            bs := Product(List(vars, i->i.range));
            bkrows := List(Flat(List(lambda.tolist(), i->i.tolist())), j->spiral.code.RulesMergedStrengthReduce(j.tolist()));
            ps := List(bkrows, _partSum);
            fps := Flat(ps);
            gcd := Gcd(List(Concat(fps, [cd1.args[1].v]), _unwrapV));
            rbase := FData(List(Flat(ps), i->TInt.value(i/gcd)));
            len := Length(ps[1]);
            rdom := rbase.at((pj * j.range +j)*len+q+1)-rbase.at((pj * j.range +j)*len+q);
            rows_data := idiv(Rows(self), gcd);
            rlambda := Lambda(pj, Lambda(j, rows_data));
            rows_by_gcd := FData(Flat(List(rlambda.tolist(), j->j.tolist() ))).at(pj * j.range +j);
            s := fTensor(fAdd(rows_by_gcd, rdom, rbase.at((pj * j.range +j)*len+q)), fId(gcd));
        elif  Length(vars) = 3 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
            pj := Filtered(vars, IsParallelLoopIndex)[1];
            ivars := Filtered(vars, i -> not IsParallelLoopIndex(i));
            j := ivars[2];
            k := ivars[1];
	    k.isSWPLoopIndex := true;
            lambda := Lambda(k, Lambda(pj, Lambda(j, Lambda(q, cd1))));
            bs := Product(List(vars, i->i.range));
            bkrows := List(Flat(List(lambda.tolist(), i->i.tolist())), j-> (List(j.tolist(),kk->Flat(spiral.code.RulesMergedStrengthReduce(kk.tolist())))));
            bkrows2 :=[];
            for i in [1..Length(bkrows)] do
               for ii in [1..Length(bkrows[i])] do
                       Add(bkrows2, bkrows[i][ii]);
               od;

            od;
            ps := List(bkrows2, _partSum);
            fps := Flat(ps);
            gcd := Gcd(List(Concat(fps, [cd1.args[1].v]), _unwrapV));
            rbase := FData(List(Flat(ps), i->TInt.value(i/gcd)));
            len := Length(ps[1]);
            idx := k * (pj.range * j.range) + (pj * j.range) + j;
            rdom := rbase.at(idx*len+q+1)-rbase.at(idx*len+q);
            rows_data := idiv(Rows(self), gcd);
            rlambda := Lambda(k, Lambda(pj, Lambda(j, rows_data)));
            rows_by_gcd := FData(Flat(List(rlambda.tolist(), j->List(j.tolist(),i->i.tolist())))).at(idx);
            s := fTensor(fAdd(rows_by_gcd, rdom, rbase.at(idx*len+q)), fId(gcd));
        else
            Error("Unsupported case -- please fix IRowDirSum.sums in spl2sums.gi:308");
        fi;
    elif IsExp(cd1) then
        vars := _sbs(Filtered(cd1.free(), IsLoopIndex), q);
        if Length(vars)=1 then
            lambda := Lambda(vars, Lambda(q, cd1));
            bs := Product(List(vars, i->i.range));
            bkrows := List(lambda.tolist(), i->i.tolist());
            ps := List(bkrows, _partSum);
            fps := Flat(ps);
            rbase := FData(List(Flat(ps), i->TInt.value(i)));
            len := Length(ps[1]);
            rdom := rbase.at(vars[1]*len+q+1)-rbase.at(vars[1]*len+q);
            rows_data := Rows(self);
            rows_by_one := FData(Lambda(vars, rows_data).tolist()).at(vars[1]);
            s := fAdd(rows_by_one, rdom, rbase.at(vars[1]*len+q));
        elif Length(vars) = 2 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
            pj := Filtered(vars, IsParallelLoopIndex)[1];
            j := Filtered(vars, i -> not IsParallelLoopIndex(i))[1];
            lambda := Lambda(pj, Lambda(j, Lambda(q, cd1)));
            bs := Product(List(vars, i->i.range));

            bkrows := List(Flat(List(lambda.tolist(), i->i.tolist())), j->spiral.code.RulesMergedStrengthReduce(j.tolist()));
            ps := List(bkrows, _partSum);
            fps := Flat(ps);
            rbase := FData(List(Flat(ps), i->TInt.value(i)));
            len := Length(ps[1]);
            rdom := rbase.at((pj * j.range +j)*len+q+1)-rbase.at((pj * j.range +j)*len+q);

            rows_data := Rows(self);
            rlambda := Lambda(pj, Lambda(j, rows_data));
            rows_by_one := FData(Flat(List(rlambda.tolist(), j->j.tolist() ))).at(pj * j.range +j);
            s := fAdd(rows_by_one, rdom, rbase.at((pj * j.range +j)*len+q));
         elif Length(vars) = 3 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
            pj := Filtered(vars, IsParallelLoopIndex)[1];
            ivars := Filtered(vars, i -> not IsParallelLoopIndex(i));
            j := ivars[2];
            k := ivars[1];
            k.isSWPLoopIndex := true;
            lambda := Lambda(k, Lambda(pj, Lambda(j, Lambda(q, cd1))));
            bs := Product(List(vars, i->i.range));
            bkrows := List(Flat(List(lambda.tolist(), i->i.tolist())), j-> (List(j.tolist(),kk->Flat(spiral.code.RulesMergedStrengthReduce(kk.tolist())))));
            bkrows2 :=[];
            for i in [1..Length(bkrows)] do
               for ii in [1..Length(bkrows[i])] do
                       Add(bkrows2, bkrows[i][ii]);
               od;

            od;
            ps := List(bkrows2, _partSum);
            fps := Flat(ps);
            rbase := FData(List(Flat(ps), i->TInt.value(i)));
            len := Length(ps[1]);
            idx := k * (pj.range * j.range) + (pj * j.range) + j;
            rdom := rbase.at(idx*len+q+1)-rbase.at(idx*len+q);
            rows_data := Rows(self);
            rlambda := Lambda(k, Lambda(pj, Lambda(j, rows_data)));
            rows_by_one := FData(Flat(List(rlambda.tolist(), j->List(j.tolist(),kk->kk.tolist())))).at(idx);
            s := fAdd(rows_by_one, rdom, rbase.at(idx*len+q));
        else
            Error("Unsupported case -- please fix IRowDirSum.sums in spl2sums.gi:344");
        fi;
    else
        Error("Unsupported case -- please fix IRowDirSum.sums in spl2sums.gi:347");
    fi;

    if IsInt(self.child(1).dims()[2]) then
        cols := Cols(self);
        bkcols := Cols(self.child(1));
        cdom :=bkcols-overlap;
        gcd := Gcd(List([cols, bkcols, cdom], _unwrapV));
        g := fTensor(fAdd(cols/gcd, bkcols/gcd, q*(cdom/gcd)), fId(gcd));
    else
        bkcols := _evInt(List(self.unrolledChildren(), i->Cols(i)));
        cols := Cols(self);
        ps := _partSum(bkcols);
        gcd := Gcd(List(Concat(ps, [cols]), _unwrapV));
        cbase := FData(List(ps, i->TInt.value(i/gcd)));
        cdom := cbase.at(q+1)-cbase.at(q);
        g := fTensor(fAdd(cols/gcd, cdom, cbase.at(q)), fId(gcd));
    fi;
    return
        ISum(self.var, self.domain,
            Compose(
                Scat(s),
                self.child(1).sums(),
                Gath(g)
            )
        );
end;

#NOTE: FF to YSV: Injecting H by default is BAD!!!
RowDirectSum.sums := meth(self)
    local c, nblocks, cols, rows, bkrows, bkcols, overlap, psr, psc, gcd, blks, bkrows_by_gcd, bkrows_by_gcd_data, vars, psr_data, psr_by_gcd,
        pj, j, k, lambda, ivars, llist, lambda2, llist2;
    overlap := self.overlap;
    nblocks  :=  self.numChildren();
    c := self.children();
    cols  :=  EvalScalar(Cols(self));
    rows := EvalScalar(Rows(self));
    bkcols := List(c, i->EvalScalar(Cols(i)));
#   FF: NOTE!! temporary hack for SAR
    bkrows := List(c, i->spiral.code.RulesStrengthReduce(Rows(i)));
    if ForAny(bkrows, IsExp) then
        bkrows := List(bkrows, i->spiral.code.RulesMergedStrengthReduce(i));
        if ForAny(bkrows, i->not (IsRec(i) and ObjId(i)=mul and IsValue(i.args[1]) and i.args[1].t=TInt)) then
            vars := Set(Flat(List(bkrows, i->Filtered(i.free(), IsLoopIndex))));
            gcd := 1;
            if Length(vars) = 1 then
                bkrows_by_gcd := List(bkrows, i->FData(Lambda(vars, i).tolist()).at(vars[1]));
                psr_data := DropLast(_partSum(bkrows), 1);
                psr_by_gcd := List(psr_data, i->When(IsExp(i), FData(Lambda(vars, i).tolist()).at(vars[1]), i));
            elif Length(vars) = 2 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
                pj := Filtered(vars, IsParallelLoopIndex)[1];
                j := Filtered(vars, i -> not IsParallelLoopIndex(i))[1];
                lambda := List(bkrows, i->Lambda(pj, Lambda(j, i)));
                llist := List([1..Length(bkrows)], k->Flat(List(lambda[k].tolist(), i->i.tolist())));
                bkrows_by_gcd := List(llist, i->fCompose(FData(i), fTensor(fBase(pj), fId(j.range))).at(j));
                psr_data := List(DropLast(_partSum(bkrows), 1), i ->spiral.code.RulesMergedStrengthReduce(i));
                lambda2 := List(psr_data, i->Lambda(pj, Lambda(j, i)));
                llist2 := List([1..Length(bkrows)], k->Flat(List(lambda2[k].tolist(), i->i.tolist())));
                psr_by_gcd := List(llist2, i->fCompose(FData(i), fTensor(fBase(pj), fId(j.range))).at(j));
            elif Length(vars) = 3  and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
                pj := Filtered(vars, IsParallelLoopIndex)[1];
                ivars := Filtered(vars, i -> not IsParallelLoopIndex(i));
                j := ivars[2];
                k := ivars[1];
                k.isSWPLoopIndex := true;
                lambda := List(bkrows, i->Lambda(k, Lambda(pj, Lambda(j, i))));
                llist := List([1..Length(bkrows)], k->Flat(List(lambda[k].tolist(),i->List(i.tolist(),j->j.tolist()))));
                bkrows_by_gcd := List(llist, i->fCompose(FData(i), fTensor(fBase(k), fBase(pj), fId(j.range))).at(j));
                psr_data := List(DropLast(_partSum(bkrows), 1), i ->spiral.code.RulesMergedStrengthReduce(i));
                lambda2 := List(psr_data, i->Lambda(k, Lambda(pj, Lambda(j, i))));
                llist2 := List([1..Length(bkrows)], k->Flat(List(lambda2[k].tolist(), i->List(i.tolist(),kk->kk.tolist()))));
                psr_by_gcd := List(llist2, i->fCompose(FData(i), fTensor(fBase(k), fBase(pj), fId(j.range))).at(j));
            else
                Error("Unsupported case -- please fix RowDirSum.sums in spl2sums.gi:409");
            fi;
        else
            vars := Set(Flat(List(bkrows, i->Filtered(i.free(), IsLoopIndex))));
            blks := List(bkrows, i->i.args[1].v);
            gcd := Gcd(blks);
            if Length(vars) = 1 then
                bkrows_by_gcd_data := List(bkrows, i->ApplyFunc(mul, Concat([i.args[1].v/gcd], Drop(i.args, 1))));
                bkrows_by_gcd := List(bkrows_by_gcd_data, i->FData(Lambda(vars, i).tolist()).at(vars[1]));
                psr_data := List(DropLast(_partSum(bkrows), 1), i ->idiv(i,gcd));
                psr_by_gcd := List(psr_data, i->When(IsExp(i), FData(Lambda(vars, i).tolist()).at(vars[1]), i));
            elif Length(vars) = 2 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
                bkrows_by_gcd_data := List(bkrows, i->spiral.code.RulesMergedStrengthReduce(ApplyFunc(mul, Concat([i.args[1].v/gcd], Drop(i.args, 1)))));
                pj := Filtered(vars, IsParallelLoopIndex)[1];
                j := Filtered(vars, i -> not IsParallelLoopIndex(i))[1];
                lambda := List(bkrows_by_gcd_data, i->Lambda(pj, Lambda(j, i)));
                llist := List([1..Length(bkrows)], k->Flat(List(lambda[k].tolist(), i->i.tolist())));
                bkrows_by_gcd := List(llist, i->fCompose(FData(i), fTensor(fBase(pj), fId(j.range))).at(j));
                psr_data := List(DropLast(_partSum(bkrows), 1), i ->spiral.code.RulesMergedStrengthReduce(idiv(i,gcd)));
                lambda2 := List(psr_data, i->Lambda(pj, Lambda(j, i)));
                llist2 := List([1..Length(bkrows)], k->Flat(List(lambda2[k].tolist(), i->i.tolist())));
                psr_by_gcd := List(llist2, i->fCompose(FData(i), fTensor(fBase(pj), fId(j.range))).at(j));
            elif Length(vars) = 3 and Length(Filtered(vars, IsParallelLoopIndex)) = 1 then
                bkrows_by_gcd_data := List(bkrows, i->spiral.code.RulesMergedStrengthReduce(ApplyFunc(mul, Concat([i.args[1].v/gcd], Drop(i.args, 1)))));
                pj := Filtered(vars, IsParallelLoopIndex)[1];
                ivars := Filtered(vars, i -> not IsParallelLoopIndex(i));
                j := ivars[2];
                k := ivars[1];
                k.isSWPLoopIndex := true;
                lambda := List(bkrows_by_gcd_data, i->Lambda(k, Lambda(pj, Lambda(j, i))));
                llist := List([1..Length(bkrows)], k->Flat(List(lambda[k].tolist(),i->List(i.tolist(),j->j.tolist()))));
                bkrows_by_gcd := List(llist, i->fCompose(FData(i), fTensor(fBase(k), fBase(pj), fId(j.range))).at(j));
                psr_data := List(DropLast(_partSum(bkrows), 1), i ->spiral.code.RulesMergedStrengthReduce(idiv(i,gcd)));
                lambda2 := List(psr_data, i->Lambda(k, Lambda(pj, Lambda(j, i))));
                llist2 := List([1..Length(bkrows)], k->Flat(List(lambda2[k].tolist(), i->List(i.tolist(),kk->kk.tolist()))));
                psr_by_gcd := List(llist2, i->fCompose(FData(i), fTensor(fBase(k), fBase(pj), fId(j.range))).at(j));

            else
                Error("Unsupported case -- please fix RowDirSum.sums in spl2sums.gi:432");
            fi;
        fi;
    else
        bkrows := List(bkrows, EvalScalar);
        gcd := Gcd(List(Concat([rows, cols, overlap], bkrows, bkcols), _unwrapV));
        bkrows_by_gcd := List(bkrows, i->i/gcd);
        psr_by_gcd := List(DropLast(_partSum(bkrows), 1), i ->idiv(i,gcd));
    fi;
    psc := DropLast(Concat([0], List(Drop(_partSum(bkcols), 1), i->i-overlap)), 1);
    return SUM( Map([1..nblocks],
        i -> Compose(
                    Scat(fTensor(fAdd(rows/gcd, bkrows_by_gcd[i], Cond(i=1, 0, i=2, bkrows_by_gcd[i-1], psr_by_gcd[i])), fId(gcd))),
                    self.child(i).sums(),
                    Gath(fTensor(fAdd(cols/gcd, bkcols[i]/gcd, psc[i]/gcd), fId(gcd))))
            ));
end;

#----------------------------------------------------------------------
IterHStack.sums := self >> let(
    bkcols := Cols(self.child(1)),
    bkrows := Rows(self.child(1)),
    nblocks := self.domain,
    cols := Cols(self), rows := Rows(self),
    ISumAcc(self.var, self.domain,
        Scat(fId(bkrows)) *
        self.child(1).sums() *
        Gath(fTensor(fBase(nblocks, self.var), fId(bkcols)))));

#-----------------------------------------------------------------------
IterVStack.sums := self >> let(
    bkcols := Cols(self.child(1)),
    bkrows := Rows(self.child(1)),
    nblocks := self.domain,
    cols := Cols(self), rows := Rows(self),
    ISum(self.var, self.domain,
        Scat(fTensor(fBase(nblocks, self.var), fId(bkrows))) *
        self.child(1).sums() *
        Gath(fId(bkcols))));

#-----------------------------------------------------------------------


text:
#############################################################################
####
##
#W  options.gi                 ACE Package                        Greg Gamble
##
##  This file installs functions and records for manipulating ACE options.
##
#Y  Copyright (C) 2000  Centre for Discrete Mathematics and Computing
#Y                      Department of Information Technology & Electrical Eng.
#Y                      University of Queensland, Australia.
##

#############################################################################
####
##
#V  KnownACEOptions . . . . . . record whose fields are the known ACE options
##  . . . . . . . . . . . . . . . . . .  each field is assigned a list of two
##  . . . . . . . . . . . . . . . . . .  components:  [leastlength, listorfn]
##
##  The known ACE options  are the RecNames of KnownACEOptions.  The value of
##  of each RecName is a list [ leastlength, listorfn ], where leastlength is
##  an integer specifying the least length of an abbreviation of the  RecName
##  that will match an ACE option,  and listorfn is either a list of  allowed
##  values or a function that can be used to test that the value of an option
##  is valid e.g. for the RecName "lookahead", we have knownOptions.lookahead
##  equal to [ 4, [0..4] ] which indicates that "look", "looka", etc. are all
##  valid abbreviations of the "lookahead" option,  and the values  that that
##  option can take are in the (integer) range 0 to 4.
##
##  If the allowed values listed for an option are 0 and 1,  then  false  and
##  true are also permitted (we translate false and true to 0 and 1, respect-
##  ively when we call ACE). The empty string signifies that ACE  expects  no
##  value for that option.
##
##  Only single-word versions of options can be used by a user of ACE via the
##  GAP interface e.g. "cc" is a synonym for "coset coincidence"  as  an  ACE
##  option,  but the latter,  being 2 words,  is  not available via  the  GAP
##  interface.
##
##  The commented out options are known ACE options that probably  won't work
##  via the GAP interface ... if the user uses these  the  interface  program
##  CALL_ACE will complain: `unknown (possibly new) or bad'  but  still  pass
##  these options to ACE ... at least the user will then know if ACE does not
##  respond as expected that the options should not be used.  We usually only
##  warn that certain options might be bad, so that this interface has a good
##  chance of still being functional if new options are added to the ACE bin-
##  ary.
##
##  Some  options  are  `GAP-introduced'  i.e. technically they are  not  ACE
##  options  ...  there is a comment beside such options;  and  they are also
##  listed in NonACEbinOptions below.
##

InstallValue(KnownACEOptions, rec(
  # aceinfile, aceignore, aceignoreunknown, acenowarnings, silent (and
  # further down: aceoutfile) are GAP-introduced options ... they  are
  # not ACE binary options.
  aceinfile := [5, IsString],
  aceignore := [5, x -> IsList(x) and ForAll(x, xi -> IsString(xi))],
  aceignoreunknown := [10, x -> IsList(x) and ForAll(x, xi -> IsString(xi))],
  acenowarnings := [6, [0,1]],
  aceecho := [7, [""]],
  aceincomment := [6, IsString],
  aceexampleoptions := [17, [0,1]],
  silent := [6, [0,1]],
  lenlex := [6, [0,1]],
  semilenlex := [10, [0,1]],
  incomplete := [10, [0,1]],
  sg := [2, IS_ACE_STRINGS],
  rl := [2, IS_ACE_STRINGS],
  aep  := [3, [1..7]],
  ai := [2, IsString],
  ao   := [2, IsString],      # "aceoutfile" is a GAP-introduced
  aceoutfile := [4, IsString],# synonym for "ao"
  asis := [2, [0,1]],
  begin := [3, [""]],         # "begin" and "start" are synomyms
  start := [5, [""]],         # ... "end" synonym omitted (it is a GAP keyword)
  bye := [3, [""]],           # "bye", "exit" and "qui" are synonyms
  exit := [4, [""]],
  qui := [1, [""]],           # the "quit" form is not available since
                              # it's a GAP keyword
  cc   := [2, x -> IsInt(x) and x > 1],
  cfactor := [1, IsInt],      # "cfactor" and "ct" are synonyms
  ct   := [2, IsInt],
  check := [5, [""]],
  redo := [4, [""]],
  compaction := [3, [0..100]],
  continu := [4, [""]],       # "continue" is a GAP 4.3+ keyword
  cycles := [2, [""]],
  dmode := [4, [0..4]],
  dsize := [4, x -> x = 0 or IsPosInt(x)],
  default := [3, [""]],
  ds := [2, IS_INC_POS_INT_LIST],
  dr := [2, IS_INC_POS_INT_LIST],
  dump := [1, x -> x in ["",0,1,2] or
                   (IsList(x) and x[1] in [0..2] and
                    (Length(x) = 1 or (Length(x) = 2 and x[2] in [0,1])))],
  easy := [4, [""]],
  echo := [4, [0,1,2]],       # hijacked! ... we don't pass this to ACE
  enumeration := [4, IsString],
  felsch := [3, ["",0,1]],
  ffactor := [1, x -> x = 0 or IsPosInt(x)],# "ffactor" and "fill"
  fill := [3, x -> x = 0 or IsPosInt(x)],   # are synonyms ... there is
                                            # no "fi" since it's a GAP
                                            # keyword
  ## Most interface functions require the next 3 ACE options to be
  ## passed as arguments rather than options
  group := [2, x -> IsInt(x) or IsString(x) or
                    (IsList(x) and
                     ForAll(x, xi -> IsString(xi) and
                                     (Length(xi) = 1) and
                                     IsLowerAlphaChar( xi[1] )))],
                                               # For group generators
  generators := [3, IS_ACE_STRINGS],           # For subgroup generators
  relators := [3, IS_ACE_STRINGS],             # For group relators

  hard := [2, [""]],
  help := [1, [""]],
  hlt  := [3, [""]],
  hole := [2, [-1..100]],
  lookahead := [4, [0..4]],
  loop := [4, x -> x = 0 or IsPosInt(x)],
  max  := [3, x -> x = 0 or (IsInt(x) and x >= 2)],
  mendelsohn := [4, [0,1]],
  messages := [4, IsInt],   # "messages" and "monitor" are synonyms
  monitor := [3, IsInt],
  mode := [2, [""]],
  nc   := [2, ["",0,1]],    # "nc" and "normal" are synonyms
  normal := [6, ["",0,1]],
  no   := [2, x -> IsInt(x) and x >= -1],
  options := [3, [""]],
  oo   := [2, IsInt],       # "oo" and "order" are synonyms
  order := [5, IsInt],
  #parameters := [3, [""]], # decommissioned ACE option
  path := [4, [0,1]],
  pmode := [4, [0..3]],
  psize := [4, x -> x = 0 or
                    (IsInt(x) and IsEvenInt(x) and IsPrimePowerInt(x))],
  sr := [2, ["",0,1,2,3,4,5]],
  print := [2, x -> x = "" or IsInt(x) or
                    (IsList(x) and Length(x) <= 3 and IsInt(x[1]) and
                     ForAll(x{[2..Length(x)]}, IsPosInt)) ],
  purec := [5, [""]],       # the ACE option is "pure c"
  purer := [5, [""]],       # the ACE option is "pure r"
  rc   := [2, x -> x = "" or IsInt(x) or
                   (IsList(x) and Length(x) <= 2 and ForAll(x, IsInt))],
  recover := [4, [""]],     # "recover" and "contiguous"
  contiguous := [6, [""]],  # are synonyms ... "rec" is
                            # not an allowed abbreviation
                            # since it's a GAP  keyword
  rep  := [2, x -> x in [1..7] or
                   (IsList(x) and Length(x) <= 2 and x[1] in [1..7] and
                    ForAll(x{[2..Length(x)]}, IsInt))],
  #restart := [7, [""]],    # decommissioned ACE option
  rfactor := [1, IsInt],    # "rfactor" and "rt" are synonyms
  rt   := [2, IsInt],
  row  := [3, [0,1]],
  sc   := [2, IsInt],       # "sc" and "stabilising" are synonyms
  stabilising := [6, IsInt],
  sims := [4, [1,3,5,7,9]],
  standard := [2, [""]],
  statistics := [4, [""]],  # "statistics" and "stats" are synonyms
  stats := [5, [""]],
  style := [5, [""]],
  subgroup := [4, IsString],
  system := [3, IsString],
  text := [4, IsString],
  time := [2, x -> IsInt(x) and x >= -1],
  tw   := [2, x -> IsList(x) and Length(x) = 2 and
                   IsInt(x[1]) and IsWord(x[2])],
  trace := [2, x -> IsList(x) and Length(x) = 2 and
                    IsInt(x[1]) and IsWord(x[2])],
  workspace := [2, x -> IsInt(x) or
                        (IsString(x) and x[Length(x)] in "0123456789kmgKMG")]
));

#############################################################################
####
##
#V  ACEOptionSynonyms . . . . . record whose fields are `preferred' known ACE
##  . . . . . . . . . . . . . . options that have synonyms.  The  values  are
##  . . . . . . . . . . . . . . . . . . . . lists of synonymous alternatives.
##
##

InstallValue(ACEOptionSynonyms, rec(
  ao   := ["aceoutfile"],
  ct   := ["cfactor"],
  fill := ["ffactor"],
  messages := ["monitor"],
  nc   := ["normal"],
  order := ["oo"],
  recover := ["contiguous"],
  rt   := ["rfactor"],
  sc   := ["stabilising"],
  tw   := ["trace"],
  stats := ["statistics"],
  start := ["begin"],
  bye  := ["exit", "qui"],
  redo := ["check"]
));

#############################################################################
####
##
#V  NonACEbinOptions . . . . . . . list of known ACE options that are not ACE
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . binary options.
##

InstallValue(NonACEbinOptions,
  [ "aceinfile",     "aceoutfile", "aceignore",    "aceignoreunknown",
    "acenowarnings", "aceecho",    "aceincomment", "aceexampleoptions",
    "echo",          "silent",     "lenlex",       "semilenlex",
    "incomplete" ]
);

#############################################################################
####
##
#V  ACE_INTERACT_FUNC_OPTIONS . . . . . list of non ACE options that are used
##  . . . . . . . . . . . . . . . . . . . .  by the interaction ACE functions
##

InstallValue(ACE_INTERACT_FUNC_OPTIONS,
  [ # used by: ACEConjugatesForNormalClosure
    "add",
    # used by: ACEOrders
    "suborder",
    # used by: ACERandomlyApplyCosetCoincidence
    "attempts", "hibound", "lobound", "subindex" ]
);

#############################################################################
####
##
#V  ACEParameterOptions . .  record whose fields are the known ACE  parameter
##  . . . . . . . . . . . .  options.  Each  field is  assigned   the   known
##  . . . . . . . . . . . .  default value, or is a record of default values.
##
##  An ACE `parameter' option, is a known ACE option for which the ACE binary
##  has a default value.  These are the `Run Parameters' that ACE lists  with
##  the `sr: 1' command,  except for  `group',  `relators'  and  `generators'
##  (which the user provides a value for via arguments rather than options).
##
##  For the case that the value of a field of the ACEParameterOptions  record
##  is itself  a  record,  the  fields  of  that  record  are  `default'  and
##  strategies for which the value assigned by that strategy differs from the
##  `default' strategy. A strategy here means a strategy option  concatenated
##  with any of its possible values (as strings).
##

InstallValue(ACEParameterOptions, rec(
  asis := 0,
  # `ct' is synonymous with `cfactor' but here we list just once.
  ct   := rec(default := 0, felsch0 := 1000, felsch1 := 1000,
              hard := 1000, purec := 1000,   sims9 := 1000),
  compaction := rec(default := 10, easy := 100, purec := 100, purer := 100),
  dmode := rec(default := 4, easy := 0,  hlt := 0,
               purer := 0,   sims1 := 0, sims5 := 0),
  dsize := rec(default := 1000),
  enumeration := "G",
  # `fill' is synonymous with `ffactor' but here we list just once.
  fill := rec(default := 0, easy := 1,  felsch0 := 1, hlt := 1,
              purec := 1,   purer := 1, sims1 := 1,   sims3 := 1,
              sims5 := 1,   sims7 := 1, sims9 := 1),
  hole := -1,
  lookahead := rec(default := 0, hlt := 1),
  loop := 0,
  max  := 0,
  mendelsohn := rec(default := 0, sims5 := 1, sims7 := 1),
  messages := 0, # Synonymous with `monitor' but here we list just once.
  no   := rec(default := -1, easy := 0,  felsch0 := 0, hlt := 0,
              purec := 0,    purer := 0, sims1 := 0,   sims3 := 0,
              sims5 := 0,    sims7 := 0, sims9 := 0),
  path := rec(default := 0),
  pmode := rec(default := 3, easy := 0,  felsch0 := 0, hlt := 0,
               purec := 0,   purer := 0, sims1 := 0,   sims3 := 0,
               sims5 := 0,   sims7 := 0, sims9 := 0),
  psize := rec(default := 256),
  # `rt' is synonymous with `rfactor' but here we list just once.
  rt   := rec(default := 0,   easy := 1000,  hard := 1,
              hlt := 1000,    purer := 1000, sims1 := 1000,
              sims3 := -1000, sims5 := 1000, sims7 := -1000),
  row  := rec(default := 1, felsch0 := 0, felsch1 := 0,
              purec := 0,   purer := 0,   sims9 := 0),
  subgroup := "H",
  time := -1,
  workspace := 1000000
));

#############################################################################
####
##
#V  ACEStrategyOptions  . list of known ACE options that are strategy options
##

InstallValue(ACEStrategyOptions,
  [ "default", "easy", "felsch", "hard", "hlt", "purec", "purer", "sims" ]
);

#############################################################################
####
##
#V  ACE_OPT_TRANSLATIONS  . . . . . record of ACE interface options for which
##  . . . . . . . . . . . . . . . . . the  ACE  binary has a different  name;
##  . . . . . . . . . . . . . . . . . its fields are the ACE interface names,
##  . . . . . . . . . . . . . . . . . its values are the  ACE  binary  names.
##

InstallValue(ACE_OPT_TRANSLATIONS, rec(
  purec := "pure c", # These first two haven't been called NonACEbinOptions
  purer := "pure r",
  aceoutfile := "ao",
  aceecho := "echo",
  aceincomment := "#"
));

#############################################################################
####
##
#V  ACE_OPT_ACTIONS . . . . . . . record of special actions  of  ACE  options
##  . . . . . . . . . . . . . . . its fields are the ACE  option  names  with
##  . . . . . . . . . . . . . . . special actions, its values are the actions
##

InstallValue(ACE_OPT_ACTIONS, rec(
  purec := "passed to ACE via option: pure c",
  purer := "passed to ACE via option: pure r",
  aceoutfile := "passed to ACE via option: ao",
  aceecho := "passed to ACE via option: echo",
  aceincomment := "passed as an ACE comment, behind a '#'",
  aceexampleoptions := "inserted by ACEExample, not passed to ACE"
));

#############################################################################
####
##
#V  ACE_ERRORS . . . . . . . . . . . . record of ACE interface error messages
##
##

InstallValue(ACE_ERRORS, rec(
  argnotopt := "should be passed as an argument, NOT an option"
));

#############################################################################
####
##
#V  ACE_OPT_SENTINELS . . . . . . . . . . . . . .  record of option sentinels
##
##  is a record whose fields are the  preferred  option  name  of  those  ACE
##  options that normally produce output and whose values are  either  `fail'
##  if there is no reliable way of detecting the last line  of  output  or  a
##  function of an input line <line> that returns `true'  if  <line>  is  the
##  last line of output expected for an option.
##

InstallValue(ACE_OPT_SENTINELS, rec(
  start := line -> Length(line) > 1 and line[ Length(line) - 1 ] = ')',
  redo  := line -> Length(line) > 1 and line[ Length(line) - 1 ] = ')',
  continu := line -> Length(line) > 1 and line[ Length(line) - 1 ] = ')',
  aep  := line -> IsMatchingSublist(line, "* P"),
  rep  := fail,
  cc   := line -> IsMatchingSublist(line, "Coset"),
  mode := line -> IsMatchingSublist(line, "start ="),
  nc   := fail,
  order := fail,
  options := line -> IsMatchingSublist(line, "  host info"),
  dump  := line -> IsMatchingSublist(line, "  #----"),
  sr    := line -> IsMatchingSublist(line, "  #----"),
  stats := line -> IsMatchingSublist(line, "  #----"),
  print := fail,
  rc   := line -> Length(line) > 12 and
                  line{[1..13]} in ["* No success;", "* An appropri",
                                    "   finite ind", "   * Unable t"],
  cycles := line -> Length(line) > 1 and line{[1..2]} in ["CO", "co"],
  recover := line -> Length(line) > 1 and line{[1..2]} in ["CO", "co"],
  standard := line -> Length(line) > 1 and line{[1..2]} in ["CO", "co"],
  sc   := fail,
  style := line -> IsMatchingSublist(line, "style ="),
  test := fail,
  tw   := line -> PositionSublist(line, "* word =") <> fail or
                  IsMatchingSublist(line, "* Trace ")
));

#############################################################################
####
##
#F  IS_INC_POS_INT_LIST . . . . . . Internal function used in KnownACEOptions
##  . . . . . . . .  returns true if argument is a single positive integer or
##  . . . . . . . . . . .  is a strictly increasing list of positive integers
##
InstallGlobalFunction(IS_INC_POS_INT_LIST,
  x -> IsPosInt(x) or (IsPosInt(x[1]) and IsSSortedList(x)));

#############################################################################
####
##
#F  IS_ACE_STRINGS  . . . . . . . . Internal function used in KnownACEOptions
##  . . . . . . . . . returns true if argument is a string or list of strings
##
InstallGlobalFunction(IS_ACE_STRINGS,
  x -> IsString(x) or (IsList(x) and ForAll(x, xi -> IsString(xi))));

#############################################################################
####
##
#F  IsKnownACEOption  . . . . . . . . Returns true if optname is a mixed case
##  . . . . . . . . . . . . . . . . . abbreviation    of    a    field     of
##  . . . . . . . . . . . . . . . . . .  KnownACEOptions, or false otherwise.
##
InstallGlobalFunction(IsKnownACEOption,
  optname -> ACEOptionData(optname).known);

#############################################################################
####
##
#F  ACEPreferredOptionName  . . . . Returns the lowercase unabbreviated first
##  . . . . . . . . . . . . . . . . alternative of optname if it is  a  known
##  . . . . . . . . . . . . . . . . ACE  option,  or  optname  in  lowercase,
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  otherwise.
##
InstallGlobalFunction(ACEPreferredOptionName,
  optname -> ACEOptionData(optname).synonyms[1]);

#############################################################################
####
##
#F  IsACEParameterOption  . . Returns true if ACEPreferredOptionName(optname)
##  . . . . . . . . . . . . . . . . . . . . is a field of ACEParameterOptions
##
InstallGlobalFunction(IsACEParameterOption,
  optname -> ACEPreferredOptionName(optname) in RecNames(ACEParameterOptions));

#############################################################################
####
##
#F  IsACEStrategyOption . . . Returns true if ACEPreferredOptionName(optname)
##  . . . . . . . . . . . . . . . . . . . . . . . .  is in ACEStrategyOptions
##
InstallGlobalFunction(IsACEStrategyOption,
  optname -> ACEPreferredOptionName(optname) in ACEStrategyOptions);

#############################################################################
####
##
#F  ACE_OPTIONS . . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . returns the options passed to an ACE interface function
##
##
InstallGlobalFunction(ACE_OPTIONS, function()
  if IsEmpty(OptionsStack) then
    return rec();
  else
    return OptionsStack[ Length(OptionsStack) ];
  fi;
end);

#############################################################################
####
##
#F  ACE_OPT_NAMES . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . .  returns option names passed to an ACE interface function
##  . . . . . . . . . . . . . if acenowarnings is not an option it also warns
##  . . . . . . . . . . . . . . . . . . . . . . . .  about deprecated options
##
InstallGlobalFunction(ACE_OPT_NAMES, function()
local optnames;
  optnames := RecNames(ACE_OPTIONS());
  if not VALUE_ACE_OPTION(optnames, false, "acenowarnings") then
    if "messfile" in optnames then
      Info(InfoACE + InfoWarning, 1,
           "ACE Warning: ",
           "Option `messfile' deprecated: use `ACEoutfile' instead");
    elif "outfile" in optnames then
      Info(InfoACE + InfoWarning, 1,
           "ACE Warning: ",
           "Option `outfile' deprecated: use `ACEinfile' instead");
    fi;
  fi;
  return optnames;
end);

#############################################################################
####
##
#F  MATCHES_KNOWN_ACE_OPT_NAME  . . . . . . . . . . . . . . Internal function
##  . . . .  returns true iff optname is a valid abbreviation of knownoptname
##  . . . . . . . . . . . . . . . . . optname should be in lowercase already!
##
InstallGlobalFunction(MATCHES_KNOWN_ACE_OPT_NAME,
function(knownoptname, optname)
  return IsMatchingSublist(knownoptname, optname) and
         KnownACEOptions.(knownoptname)[1] <= Length(optname);
end);

#############################################################################
####
##
#F  FULL_ACE_OPT_NAME . . . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . sets opt.fullname to be the unabbreviated version of opt.name
##  . . . . . . . . . . .  if one exists among the fields of KnownACEOptions,
##  . . . . . . . . . . . . . . in which case, opt.known is also set to true;
##  . . . . . . . . . . .  otherwise,  opt.fullname  is set  to  opt.name  in
##  . . . . . . . . . . . . . . lower case,  and  opt.known  is set to false.
##
InstallGlobalFunction(FULL_ACE_OPT_NAME, function(opt)
local lcaseoptname, list;
  lcaseoptname := LowercaseString(opt.name);
  list := Filtered(RecNames(KnownACEOptions),
                   s -> MATCHES_KNOWN_ACE_OPT_NAME(s, lcaseoptname));
  opt.known := not( IsEmpty(list) );
  if opt.known then
    opt.fullname := list[1];  # We assume any match is unique!
  else
    opt.fullname := lcaseoptname;
  fi;
end);

#############################################################################
####
##
#F  ACE_OPTION_SYNONYMS . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . . returns a list of synonyms of optname
##
##
InstallGlobalFunction(ACE_OPTION_SYNONYMS, function(optname)
local list, recname;
  list := [ optname ];
  for recname in RecNames(ACEOptionSynonyms) do
    if recname = optname or optname in ACEOptionSynonyms.(recname) then
      list := Concatenation( [ recname ], ACEOptionSynonyms.(recname) );
      break;
    fi;
  od;
  return list;
end);

#############################################################################
####
##
#F  ACE_IF_EXPR . . . . . . . . . . . . . . . . . . . . . .  An expression if
##
##
InstallGlobalFunction(ACE_IF_EXPR, function(bool, trueval, falseval, failval)
  if bool = true then
    return trueval;
  elif bool = false then
    return falseval;
  else
    return failval;
  fi;
end);

#############################################################################
####
##
#F  ACE_VALUE_OPTION  . . . . . . . . Essentially an extension of ValueOption
##  . . . . . . . . . . . . . . .  but also removes optname from OptionsStack
##
##  ACE_VALUE_OPTION(optname,  defaultval)  returns  ValueOption(optname)  if
##  optname is set and defaultval, otherwise.
##
##  ACE_VALUE_OPTION(optname, val,  trueval,  elseval).  If optname has value
##  val then return trueval else return elseval.
##
##  If ACE_VALUE_OPTION is called with a different no. of aguments to 1 or 2,
##  all but  the  first  argument  is  ignored,  and  ValueOption(arg[1])  is
##  returned. Calling ACE_VALUE_OPTION with no arguments is an error.
##
InstallGlobalFunction(ACE_VALUE_OPTION, function(arg)
local optval;
  optval := ValueOption(arg[1]);
  if not IsEmpty(OptionsStack) then
    Unbind( OptionsStack[ Length(OptionsStack) ].(arg[1]) );
  fi;
  if Length(arg) = 2 then
    return ACE_IF_EXPR(optval <> fail, optval, arg[2], arg[2]);
  elif Length(arg) = 4 then
    return ACE_IF_EXPR(optval = arg[2], arg[3], arg[4], arg[4]);
  elif not IsEmpty(arg) then
    # Ignore all but the first argument
    return optval;
  fi;
end);

#############################################################################
####
##
#F  ACE_VALUE_OPTION_ERROR(<optrec>, <optname>, <defaultval>, <IsOK>, <errmsg>)
##
##  returns:
##    `false' if `ValueOption(<option>) = fail'
##               (and sets `<optrec>.(<optname>) := <defaultval>') or
##            if `<IsOK>( ValueOption(<option>) )'
##               (and sets `<optrec>.(<optname>) := ValueOption(<option>)')
##    `true'  if `not <IsOK>( ValueOption(<option>) )'
##               (and sets `<optrec>.errmsg := [<errmsg>]')
##
InstallGlobalFunction(ACE_VALUE_OPTION_ERROR,
function(optrec, optname, defaultval, IsOK, errmsg)
local optval;
  optval := ValueOption(optname);
  if optval = fail then
    optrec.(optname) := defaultval;
  elif IsOK(optval) then
    optrec.(optname) := optval;
  else
    optrec.errmsg := [errmsg];
    return true;
  fi;
  return false;
end);

#############################################################################
####
##
#F  VALUE_ACE_OPTION  . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . checks among optnames for any settings of synonyms of optnm
##  . . . . . . . (or if optnm is a list  any  synonyms  of  the  members  of
##  . . . . . . . optnm). The latest such optname in  optnames  will  prevail
##  . . . . . . . and its value will be returned. Otherwise, if  there  isn't
##  . . . . . . . . . . . . . . . .  such an optname, defaultval is returned.
##
InstallGlobalFunction(VALUE_ACE_OPTION, function(optnames, defaultval, optnm)
local optname, optval, optnmlist;
  optval := defaultval;
  if IsString(optnm) then
    optnmlist := [ optnm ];
  else
    optnmlist := optnm; # This situation is special ... useful for checking
                        # whether a list of options have been set
  fi;
  optnmlist := Union( List(optnmlist,
                           optname -> ACE_OPTION_SYNONYMS(optname)) );
  for optname in Filtered(optnames,
                          optname -> ForAny(optnmlist,
                                            s ->
                                            MATCHES_KNOWN_ACE_OPT_NAME(
                                                s,
                                                LowercaseString(optname)
                                                )
                                            ))
  do
    optval := ValueOption(optname);
  od;
  return optval;
end);

#############################################################################
####
##
#F  DATAREC_VALUE_ACE_OPTION  . . . . . . . . . . . . . . . Internal function
##  . . . . . . . checks among RecNames(datarec.options) for any settings  of
##  . . . . . . . synonyms of optnm The latest such optname prevails and  its
##  . . . . . . . value is  returned.  Otherwise,  if  there  isn't  such  an
##  . . . . . . . optname  or  datarec.options  is  unbound,  defaultval   is
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . returned.
##
InstallGlobalFunction(DATAREC_VALUE_ACE_OPTION,
                      function(datarec, defaultval, optnm)
local optname, optval;
  optval := defaultval;
  if IsBound(datarec.options) then
    for optname in Filtered(RecNames(datarec.options),
                            optname -> ForAny(ACE_OPTION_SYNONYMS(optnm),
                                              s ->
                                              MATCHES_KNOWN_ACE_OPT_NAME(
                                                  s,
                                                  LowercaseString(optname)
                                                  )
                                              ))
    do
      optval := datarec.options.(optname);
    od;
  fi;
  return optval;
end);

#############################################################################
####
##
#F  ACE_COSET_TABLE_STANDARD  . . . . . . Return either the user's choice for
##  . . . . . . . . . . . . . . . . . . . the CosetTableStandard or,  if  the
##  . . . . . . . . . . . . . . . . . . . user has made no choice,  a  string
##  . . . . . . . . . . . . . . . . . . . representing   the   current    GAP
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . standard.
##
##  A check among options for any settings of `lenlex' or  `semilenlex'.  The
##  latest such optname that is set to true is returned, or if  there  is  no
##  such setting a string representing the current GAP default  is  returned:
##  for  GAP  4.2  "GAPsemilenlex"  was  returned;  since  GAP   4.3,   "GAP"
##  concatenated  with  the  value  of  `CosetTableStandard'   (by   default,
##  "lenlex") is returned.
##
InstallGlobalFunction(ACE_COSET_TABLE_STANDARD, function(options)
local optname;
  for optname in Filtered(Reversed( RecNames(options) ),
                          optname -> ForAny(["lenlex", "semilenlex"],
                                            s ->
                                            MATCHES_KNOWN_ACE_OPT_NAME(
                                                s,
                                                LowercaseString(optname)
                                                )
                                            ))
  do
    if options.(optname) = true then
      return ACEPreferredOptionName(optname);
    fi;
  od;
  return Concatenation("GAP", CosetTableStandard);
end);

#############################################################################
####
##
#F  ACE_VALUE_ECHO  . . . . . . . . . . . . . . . . . . . . Internal function
##
##
InstallGlobalFunction(ACE_VALUE_ECHO, function(optnames)
local echoval;
  echoval := VALUE_ACE_OPTION(optnames, 0, "echo");
  if echoval in KnownACEOptions.echo[2] then
    return echoval;
  else
    return ACE_IF_EXPR(echoval = true, 1, 0, 0);
  fi;
end);

#############################################################################
####
##
#F  TO_ACE_GENS . . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . from the GAP free group generators fgens  returns
##  . . . . . . . . . . . . a record used to create the equivalent ACE  group
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  generators
##
##  Returns a record with fields:
##
##    acegens
##        the ACE equivalent of fgens; and
##
##    toace
##        the ACE directive string needed for the `group' option so that  ACE
##        uses acegens for its generators.
##
InstallGlobalFunction(TO_ACE_GENS, function(fgens)
local n, acegens;

  n := Length(fgens);
  # Define the generators ACE will use
  if n <= 26 then
    # if #generators <= 26 tell ACE to use alphabetic generators: a ...
    if ForAll(fgens, function(g)
                       local gstring;
                       gstring := String(g);
                       return Length(gstring) = 1 and
                              LowercaseString(gstring) = gstring;
                     end)
    then
      # if all generators are represented by single lowercase letters
      # ... use the user's set of generators for ACE
      acegens := List(fgens, g -> String(g));
    else
      acegens := List([1..n], i -> WordAlp(CHARS_LALPHA, i));
    fi;
    return rec(acegens := acegens, toace := Flat(acegens));
  else
    # if #generators > 26 tell ACE to use numerical generators: 1 ...
    return rec(acegens := List([1..n], i -> String(i)), toace := n);
  fi;
end);

#############################################################################
####
##
#F  ACE_WORDS . . . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . .  returns the translation of words in generators fgens
##  . . . . . . . . . . .  to words in ACEgens (the generators ACE will use),
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  as one string.
##
InstallGlobalFunction(ACE_WORDS, function(words, fgens, ACEgens)
  words := ACE_WORDS_ARG_CHK(fgens, words, "");
  return JoinStringsWithSeparator(
             List(words, w -> String( MappedWord( w,
                                                  fgens,
                                                  GeneratorsOfGroup(
                                                      FreeGroup(ACEgens)
                                                      ) ) ) ) );
end);

#############################################################################
####
##
#F  ACE_RELS  . . . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . returns the translation of  the  relators  rels  in
##  . . . . . . . . . . . generators  fgens  to   words   in   ACEgens   (the
##  . . . . . . . . . . . generators ACE will use), as  one  string,  but  if
##  . . . . . . . . . . . enforceAsis is true  ensure  the  relator  for  the
##  . . . . . . . . . . . first generator (which we'll  represent  as  x)  is
##  . . . . . . . . . . . . . . . . .  translated as "x*x" rather than "x^2".
##
InstallGlobalFunction(ACE_RELS, function(rels, fgens, ACEgens, enforceAsis)
  if enforceAsis then
    return Concatenation( ACEgens[1], ACEgens[1], ", ",
                          ACE_WORDS(Filtered(rels, rel -> rel <> fgens[1]^2),
                                    fgens, ACEgens) );
  else
    return ACE_WORDS(rels, fgens, ACEgens);
  fi;
end);

#############################################################################
####
##
#F  ToACEGroupGenerators  . . . . . Given the GAP free group generators fgens
##  . . . . . . . . . . . . . . . . returns the ACE directive  string  needed
##  . . . . . . . . . . . . . . . . for the `group' option so that  ACE  uses
##  . . . . . . . . . . . . . . . . an   appropriate   equivalent   set    of
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . generators.
##
InstallGlobalFunction(ToACEGroupGenerators, function(fgens)

  fgens := ACE_FGENS_ARG_CHK(fgens);
  return TO_ACE_GENS(fgens).toace;
end);

#############################################################################
####
##
#F  ToACEWords  . . . .  Returns the translation of words in generators fgens
##  . . . . . . . . . .  to equivalent ACE words, as one string, suitable for
##  . . . . . . . . . . . . . . . .  the `relators' and `generators' options.
##
InstallGlobalFunction(ToACEWords, function(fgens, words)

  fgens := ACE_FGENS_ARG_CHK(fgens);
  return ACE_WORDS(words, fgens, TO_ACE_GENS(fgens).acegens);
end);

#############################################################################
####
##
#F  ACE_FGENS_ARG_CHK( <fgens> )
##
##  Checks that <fgens> is a list of free group generators for the same  free
##  group, gives the user a chance to fix them if necessary, and then returns
##  the (repaired) <fgens>.
##
InstallGlobalFunction(ACE_FGENS_ARG_CHK, function(fgens)
local errmsg, onbreakmsg, error, fam;

  onbreakmsg :=
      ["Type: 'quit;' to quit to outer loop, or",
       "type: 'fgens := <val>; return;' to assign <val> to fgens to continue."];
  error := true;
  repeat
    if not IsList(fgens) then
        errmsg := ["fgens must be a *list* of free group gen'rs"];
    elif not ForAll(fgens, g -> IsAssocWordWithInverse(g) and
                                (NumberSyllables(g) = 1) and
                                (ExponentSyllable(g, 1) = 1)) then
      if ForAll(fgens, IsElementOfFpGroup) then
        errmsg := ["fgens must be a list of free group gen'rs,",
                   "not fp group elements e.g. use 'FreeGeneratorsOfFpGroup'",
                   "rather than 'GeneratorsOfGroup'"];
      else
        errmsg := ["fgens must be a list of free group gen'rs"];
      fi;
    else
      fam := FamilyObj(fgens[1]);
      if not ForAll(fgens{[2..Length(fgens)]}, g -> fam = FamilyObj(g)) then
        errmsg := ["fgens must all belong to the same free group"];
      else
        error := false;
      fi;
    fi;
    if error then
      Error(ACE_ERROR(errmsg, onbreakmsg), "\n");
    fi;
  until not error;
  return fgens;
end);

#############################################################################
####
##
#F  ACE_WORDS_ARG_CHK( <fgens>, <words>, <whicharg> )
##
##  Checks that <words> is a valid list of words in the free group generators
##  <fgens>. If not, an error message for  the  <whicharg>  (which  indicates
##  what type of words they are,  e.g.  "relators",  "subgp  gen'rs"  or  "")
##  argument is generated, telling the user how  to  fix  the  problem.  Once
##  everything is ok, <words> after being filtered of any  identity  elements
##  is returned.
##
InstallGlobalFunction(ACE_WORDS_ARG_CHK, function(fgens, words, whicharg)
local fam, errmsg, onbreakmsg;

  onbreakmsg :=
      ["Type: 'quit;' to quit to outer loop, or",
       "type: 'words := <val>; return;' to assign <val> to words to continue.",
       "Note: fgens is the list of free group generators."];

  fam := FamilyObj(fgens[1]);
  errmsg := "words ";
  if whicharg <> "" then
    errmsg := Concatenation(errmsg, "(", whicharg, ") ");
  fi;
  while not IsList(words) or not ForAll(words, w -> FamilyObj(w) = fam) do
    if IsList(words) and ForAll(words, IsElementOfFreeGroup) then
      errmsg :=
        [Concatenation(
             errmsg, "is a list of words in the *wrong* free grp gen'rs")];
    elif IsList(words) and ForAll(words, IsElementOfFpGroup) then
      errmsg :=
        [Concatenation(
             errmsg, "must be a list of words in the free group gen'rs,"),
         "not fp group elements. Perhaps, you should use 'UnderlyingElement'",
         "to convert each fp group element to a word in the free group gen'rs"];
    else
      errmsg :=
        [Concatenation(
             errmsg, "must be a list of words in the free group gen'rs")];
    fi;
    Error(ACE_ERROR(errmsg, onbreakmsg), "\n");
  od;
  return Filtered(words, word -> not IsOne(word));
end);

#############################################################################
####
##
#F  PROCESS_ACE_OPTIONS . . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . for the ACE function with name ACEfname process options
##  . . . . . . . . . (on the top of OptionsStack)  with  names  newoptnames,
##  . . . . . . . . . other than those  that  are  fields  of  disallowed  or
##  . . . . . . . . . listed in ignored, by sending them to ACE via the write
##  . . . . . . . . . function ToACE,  after  appropriate  translation  where
##  . . . . . . . . . necessary, mostly in the order specified by  the  user.
##  . . . . . . . . . The list optnames contains the names of  all  currently
##  . . . . . . . . . active options i.e. the fields of all options on top of
##  . . . . . . . . . the OptionsStack. If  echo  is  set  then  all  options
##  . . . . . . . . . processed are echoed along with an  indication  of  how
##  . . . . . . . . . they were handled by the interface. If the InfoLevel of
##  . . . . . . . . . InfoACE or InfoWarning is at least 1 and the  user  has
##  . . . . . . . . . not passed the  acenowarnings  option  then  a  warning
##  . . . . . . . . . message is issued for each optname that is a  field  of
##  . . . . . . . . . disallowed or is in ignored or for some other reason is
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  ignored.
##
InstallGlobalFunction(PROCESS_ACE_OPTIONS,
function(ACEfname, optnames, newoptnames, echo, datarec, disallowed, ignored)
local ToACE, IsValidOptionValue, CheckValidOption, ProcessOption,
      AddIgnoreOptionsToIgnored, IsMyLine, nowarnings, ignoreunknown,
      paramoptnames, strategy, opt, optname, line, invokesEnumeration;

  ToACE := function(list)
    WRITE_LIST_TO_ACE_STREAM(datarec.stream, list);
  end;

  IsValidOptionValue := function(val)
    # Check that val is a valid value of opt.fullname.
    # This function will only be called when opt.known = true,
    # in which case, opt.fullname will be a field of KnownACEOptions
    if IsFunction(KnownACEOptions.(opt.fullname)[2]) then
      return KnownACEOptions.(opt.fullname)[2](val);
    elif IsBool(val) then
      return KnownACEOptions.(opt.fullname)[2] in ["", ["",0,1], [0,1]];
    else
      return val in KnownACEOptions.(opt.fullname)[2];
    fi;
  end;

  CheckValidOption := function(val)
    # If opt.fullname is a known allowed optname and val is a valid value,
    # warn the user of a possible error, if s/he wants to know and its
    # not an ignored option.
    if not(nowarnings or opt.ignore) then
      if opt.fullname in RecNames(disallowed) then
        Info(InfoACE + InfoWarning, 1,
             "ACE Warning: ", opt.name, ": ", disallowed.(opt.fullname));
      elif opt.known then
        if not IsValidOptionValue(val) then
          Info(InfoACE + InfoWarning, 1,
               "ACE Warning: ", val, ": ",
               "possibly not an allowed value of ", opt.name);
        fi;
      else
        Info(InfoACE + InfoWarning, 1,
             "ACE Warning: ", opt.name, ": unknown (maybe new) or bad option");
      fi;
    fi;
  end;

  ProcessOption := function(val)
    # Echo what we are about to do first, if the user has set the echo
    # option.
    if echo > 0 then
      if opt.ignore then
        Print(" ", opt.name, " := ", opt.value, " (ignored)\n");
      elif opt.fullname in RecNames(ACE_OPT_ACTIONS) then
        Print(" ", opt.name);
        if val = "" then
          Print(" (no value, ");
        else
          Print(" := ", opt.value, " (");
        fi;
        Print( ACE_OPT_ACTIONS.(opt.fullname), ")\n" );
      elif opt.fullname in NonACEbinOptions then
        Print(" ", opt.name, " := ", opt.value, " (not passed to ACE)\n");
      elif opt.list then
        Print(" ", opt.name, " := ", opt.value,
              " (brackets are not passed to ACE)\n");
      elif val = "" then
        Print(" ", opt.name, " (no value)\n");
      else
        Print(" ", opt.name, " := ", val, "\n");
      fi;
    fi;
    # Warn user if opt.name is an unknown optname or has an unexpected value
    # if they want to know.
    CheckValidOption(val);
    # Now do it ... pass opt.ace (which is opt.name except when the ACE and
    # GAP optnames differ) to ACE with value val,  except if opt.name is to
    # be ignored or is a NonACEbinOption without a translation.
    if not opt.donotpass and not opt.ignore then
      if opt.fullname in RecNames(ACE_OPT_TRANSLATIONS) then
        # The ACE optname differs from the GAP optname
        opt.ace := ACE_OPT_TRANSLATIONS.(opt.fullname);
      else
        # The ACE optname is the same as the GAP optname
        opt.ace := opt.name;
      fi;
      if opt.list then
        ToACE([ opt.ace,":",
                JoinStringsWithSeparator( List(val, String) ), ";" ]);
      elif val = "" then
        ToACE([ opt.ace, ";" ]);
      elif opt.fullname = "aceincomment" then
        ToACE([ opt.ace, val, ";" ]);
      else
        ToACE([ opt.ace, ":", val, ";" ]);
      fi;

      # Eventually we may include more general support for interpretation
      # of ACE output here ... for the moment we ensure the enumeration
      # result is set (for ACEStats) and the coset table is set (for
      # ACECosetTable[FromGensAndRels]) if there is an enumeration result
      if IsBound(datarec.procId) then
        if not IsBound( ACE_OPT_SENTINELS.(opt.synonyms[1]) ) then
          # Flush any available output ... it may contain errors
          line := ReadAllLine(datarec.stream);
          while line <> fail do
            Info(InfoACE + InfoWarning, 1, Chomp(line));
            line := ReadAllLine(datarec.stream);
          od;
        elif opt.fullname = "print" and IsBound(datarec.stats) and
             val in [ "", datarec.stats.activecosets ] and
             (datarec.stats.index <> 0 or
              VALUE_ACE_OPTION(optnames, false, "incomplete") ) then
          datarec.cosettable := ACE_COSET_TABLE(datarec.stats.activecosets,
                                                datarec.acegens,
                                                datarec.stream,
                                                ACE_READ_NEXT_LINE);
        else
          if ACE_OPT_SENTINELS.(opt.synonyms[1]) = fail then
            ToACE([ "text:***" ]);
            IsMyLine := line -> IsMatchingSublist(line, "***");
          else
            IsMyLine := ACE_OPT_SENTINELS.(opt.synonyms[1]);
          fi;
          invokesEnumeration := opt.synonyms[1] in
                                ["start", "continu", "redo", "aep", "rep"];
          repeat
            line := ACE_READ_NEXT_LINE(datarec.stream);
            if invokesEnumeration and
               not IsMatchingSublist(line, "** ERROR") and
               Length(line) > 1 and line[ Length(line) - 1 ] = ')' then
              datarec.enumResult := Chomp(line);
              datarec.stats := ACE_STATS(datarec.enumResult);
            fi;
            Info(InfoACE + InfoWarning, 1, Chomp(line));
          until IsMyLine(line);
        fi;
      fi;

    fi;
  end;

  AddIgnoreOptionsToIgnored := function()
  local ignore, optname, opt;
    ignore := VALUE_ACE_OPTION(optnames, [], "aceignore");
    for optname in ignore do
      opt := rec(name := optname);
      FULL_ACE_OPT_NAME(opt); # sets opt.known and opt.fullname
      Add(ignored, opt.fullname);
    od;
  end;

  if echo > 0 then
    Print(ACEfname, " called with the following options:\n");
    if echo = 2 then
      paramoptnames := RecNames(ACEParameterOptions);
      strategy := "default";
    fi;
  fi;

  nowarnings := VALUE_ACE_OPTION(optnames, false, "acenowarnings");
  ignoreunknown := VALUE_ACE_OPTION(optnames, ACEIgnoreUnknownDefault,
                                    "aceignoreunknown");
  AddIgnoreOptionsToIgnored();

  for optname in newoptnames do
    opt := ACEOptionData(optname); # sets opt.name, opt.known, opt.fullname
                                   # and opt.synonyms
    opt.value := ValueOption(opt.name);
    if echo = 2 then
      paramoptnames := Difference(paramoptnames, opt.synonyms);
      if opt.fullname in ACEStrategyOptions then
        strategy := opt.fullname;
        if IsInt(opt.value) then
          strategy := Concatenation(strategy, String(opt.value));
        elif opt.value and (opt.fullname = "felsch") then
          strategy := "felsch0";     # Hmm! I'd like to do this differently!!
        fi;
      fi;
    fi;
    # We don't pass the NonACEbinOptions options to ACE unless they
    # have a translation (i.e. are fields of ACE_OPT_TRANSLATIONS)
    opt.donotpass := (opt.fullname in NonACEbinOptions) and
                     not (opt.fullname in RecNames(ACE_OPT_TRANSLATIONS));
    opt.ignore := opt.fullname in RecNames(disallowed) or
                  opt.fullname in ignored or
                  (ignoreunknown and not opt.known);
    opt.list := false;
    if opt.value = true then
      # An option detected by GAP as boolean may in fact be a no-value
      # option of ACE ... unknown ACE options detected as being true are
      # assumed to be no-value options (since the user can still over-ride
      # this behaviour by entering values of 0 or 1 explicitly e.g.
      # ACEStats(... : `opt' := 1) )
      if not opt.known or IsValidOptionValue("") then
        ProcessOption("");
      else
        ProcessOption(1);
      fi;
    elif opt.value = false then
      ProcessOption(0);
    elif not IsString(opt.value) and IsList(opt.value) then
      opt.list := true;
      ProcessOption(opt.value);
    else
      ProcessOption(opt.value);
    fi;
  od;

  if echo = 2 then
    Print("Other options set via ACE defaults:\n");
    for optname in paramoptnames do
      Print(" ", optname, " := ");
      if IsRecord(ACEParameterOptions.(optname)) then
        if IsBound(ACEParameterOptions.(optname).(strategy)) then
          Print(ACEParameterOptions.(optname).(strategy), "\n");
        else
          Print(ACEParameterOptions.(optname).default, "\n");
        fi;
      else
        Print(ACEParameterOptions.(optname), "\n");
      fi;
    od;
  fi;

end);

#############################################################################
####
##
#F  PROCESS_ACE_OPTION  . . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . . . . . . . . . process  a  single  ACE  option
##  . . . . . . . . . . . . . . . . . . . . . that  hasn't  been  passed  via
##  . . . . . . . . . . . . . . . . . . . . . . . . .  GAP's option mechanism
##
##  Checks optval is a valid value of optname (which must  be  lowercase  and
##  unabbreviated) and pass it ACE by writing to stream.
##
InstallGlobalFunction(PROCESS_ACE_OPTION, function(stream, optname, optval)
local aceoptname, error;

  # Check that optval is a valid value of optname.
  if IsFunction(KnownACEOptions.(optname)[2]) then
    error := not KnownACEOptions.(optname)[2](optval);
  else
    error := not (optval in KnownACEOptions.(optname)[2]);
  fi;

  if error then
    Info(InfoACE + InfoWarning, 1,
         "ACE Warning: ", optval, ": ",
         "possibly not an allowed value of ", optname);
  fi;

  if optname in RecNames(ACE_OPT_TRANSLATIONS) then
    # The ACE optname differs from the GAP optname
    aceoptname := ACE_OPT_TRANSLATIONS.(optname);
  else
    # The ACE optname is the same as the GAP optname
    aceoptname := optname;
  fi;

  if optval = "" then
    WRITE_LIST_TO_ACE_STREAM(stream, [ aceoptname, ";" ]);
  elif not IsString(optval) and IsList(optval) then
    WRITE_LIST_TO_ACE_STREAM(
        stream, [ aceoptname,":",
                  JoinStringsWithSeparator( List(optval, String) ), ";" ]
        );
  else
    WRITE_LIST_TO_ACE_STREAM(stream, [ aceoptname, ":", optval, ";" ]);
  fi;

  return error;
end);

#############################################################################
####
##
#F  ACEOptionData . . .  returns a record of the known data of an option name
##
##  For argument optname the fields of the returned record are:
##    name  . . . .  optname (unchanged);
##    known . . . .  true iff optname is a valid mixed case abbreviation of a
##                   KnownACEOption field;
##    fullname  . .  the lower case unabbreviated  form  of  optname  if  the
##                   `known' field is set `true',  or optname in  lower case,
##                   otherwise;
##    synonyms  . .  a list of KnownACEOptions fields that are  option  names
##                   synonymous with optname, if the  `known'  field  is  set
##                   set `true', or list with just fullname otherwise;
##    abbrev  . . .  the shortest lowercase abbreviation of  optname  if  the
##                   `known' field is set `true', or fullname otherwise.
##
InstallGlobalFunction(ACEOptionData, function(optname)
local opt;
  opt := rec(name := optname);
  FULL_ACE_OPT_NAME(opt); # Sets the `known' and `fullname' fields
  if opt.known then
    opt.synonyms := ACE_OPTION_SYNONYMS(opt.fullname);
    opt.abbrev := opt.fullname{[1 ..  KnownACEOptions.(opt.fullname)[1]]};
  else
    opt.synonyms := [ opt.fullname ];
    opt.abbrev := opt.fullname;
  fi;
  return opt;
end);

#############################################################################
####
##
#F  SANITISE_ACE_OPTIONS  . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . . . . . . . . . . . .  Called by SetACEOptions,
##  . . . . . . . . . . . . . . . . . or by CALL_ACE when CALL_ACE is invoked
##  . . . . . . . . . . . . . . . . . by   ACEExample   with   user   options
##
##  Scrubs any option  names  in  optsrec  that match  those  in  newoptsrec,
##  to ensure that *all* new options are at the end of  optsrec  when  it  is
##  updated with options from newoptsrec.
##
InstallGlobalFunction(SANITISE_ACE_OPTIONS, function(optsrec, newoptsrec)
local newoptnames, optname, opt;
    newoptnames := Concatenation(
                       List(RecNames(newoptsrec),
                            optname -> ACEOptionData(optname).synonyms)
                       );
    for optname in RecNames(optsrec) do
      opt := rec(name := optname);
      FULL_ACE_OPT_NAME(opt); # Sets opt.fullname
      if opt.fullname in newoptnames then
        Unbind(optsrec.(optname));
      fi;
    od;
end);

#############################################################################
####
##
#F  NEW_ACE_OPTIONS()
##
##  Looks at OptionsStack and returns the new options.
##
InstallGlobalFunction(NEW_ACE_OPTIONS, function()
local newoptions, oldoptions, oldnames, optname;
    if IsEmpty(OptionsStack) then
      return rec();
    elif Length(OptionsStack) = 1 then
      return OptionsStack[ Length(OptionsStack) ];
    else
      newoptions := ShallowCopy( OptionsStack[ Length(OptionsStack) ] );
      oldoptions := OptionsStack[ Length(OptionsStack) - 1 ];
      oldnames := RecNames(oldoptions);
      for optname in RecNames(newoptions) do
        if optname in oldnames and
           oldoptions.(optname) = newoptions.(optname) then
          Unbind( newoptions.(optname) );
        fi;
      od;
      return newoptions;
    fi;
end);

#E  options.gi  . . . . . . . . . . . . . . . . . . . . . . . . . . ends here

max_stars_repo_name: isuruf/ace

max_issues_repo_name: isuruf/ace

max_forks_repo_name: isuruf/ace


text:
Class(AParMPI, AGenericTag,
    rec(isParMPI := true)
);

Class(MPIProcGridND, AGenericTag,
    rec(isParMPI := true)
);

max_stars_repo_name: spiral-software/spiral-package-mpi

max_issues_repo_name: spiral-software/spiral-package-mpi

max_forks_repo_name: spiral-software/spiral-package-mpi


text:
Walk := function(name, op)
	local dir, file, e;
	dir := Directory(name);
	for e in SortedList(DirectoryContents(name)) do
		file := Filename(dir, e);
		if IsDirectoryPath(file) then
			if not (e in [".", ".."]) then
				Walk(file, op);
			fi;
		else
			op(file);
		fi;
	od;
end;

# This will print filenames
Walk(".", Display);

max_stars_repo_name: mullikine/RosettaCodeData