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

_DHT_CONST := 2.5;

#F TDHT(<n>, <k>) - RDFT Nonterminal
Class(TDHT, TaggedNonTerminal, rec(
    abbrevs := [ (n) -> Checked(IsPosInt(n),  [n]) ],
    dims := self >> [self.params[1], self.params[1]],
    isReal := True,
    terminate := self >> PkDHT1(self.params[1]).terminate(),
    SmallRandom := () -> Random([2,4,6,8,10,12,16,18,24,30,32]),
    normalizedArithCost := (self) >> let(n := self.params[1], IntDouble(_DHT_CONST * n * d_log(n) / d_log(2)))
));

_DHTtoRDFT := N -> DirectSum(I(2), Tensor(I(N/2-1), F(2)));

#F TXMatDHT(<n>) - X-matrix to translate a RC(DFT(N)) -> DHT(2*N)
Class(TXMatDHT, TaggedNonTerminal, rec(
    abbrevs := [ (n) -> Checked(IsPosIntSym(n), [n]) ],
    dims := self >> [self.params[1], self.params[1]],
    isReal := True,
    terminate := self >>  _DHTtoRDFT(self.params[1]) * TConjEven(self.params[1]).terminate(),
    SmallRandom := () -> Random([2,4,6,8,10,12,16,18,24,30,32]),
    doNotMeasure := true
));

#F  Translate DHT into RDFT
NewRulesFor(TDHT, rec(
    DHT_DFT_tSPL := rec(
        switch := true,
        applicable := (self, t) >> IsEvenInt(t.params[1]), # and t.hasTags(),
        children := (self, t) >> let(N := t.params[1], tags := t.getTags(),
            [[
                TXMatDHT(N).withTags(tags),
                TRC(DFT(N/2)).withTags(tags)
            ]]),
        apply := (self, t, C, Nonterms) >> C[1] * C[2]
    )
));

NewRulesFor(TXMatDHT, rec(
    TXMatDHT_TConjEven := rec(
        switch := true,
        applicable := (self, t) >> IsEvenInt(t.params[1]),
        children := (self, t) >> let(N := t.params[1], tags := t.getTags(),
            [[
                 TConjEven(N).withTags(tags),
           ]]),
        apply := (self, t, C, Nonterms) >> _DHTtoRDFT(t.params[1]) * C[1]
    )
));


#
# Read("~/Workspace/Chevalley.gap/init.gi"); Read(Filename(home_dir,"load.gi")); Read(Filename(test_dir,"chvadj.test.gi"));
#
# Read("~/Workspace/Chevalley.gap/init.gi"); Read(Filename(test_dir,"chvadj.test.init.gi")); Read(Filename(test_dir,"chvadj.test.gi"));
#
# Read(Filename(test_dir,"chvadj.test.gi"));
#

sys:=ChevalleyAdj("F",4,GF(2));
Print("Created ChevalleyAdj ",sys,"\n");
Print("\t[",IsChevalleyAdj(sys)=true,"] IsChevalleyAdj(sys)=true\n");


InstallMethod(NilpotentChv,
              "NilpotentChv element in simple Lie type",
              [IsChevalleyAdj,IsList],
              function(sys,coeffs)
              local object,
              B,admat,l;

              if Filtered(coeffs,i->not i[2] in Integers)=[] then
                  coeffs:=List(coeffs,i->[i[1],One(ring(sys))*i[2]]);
              elif Filtered(coeffs,i->not i[2] in ring(sys))<>[] then
                  Error("NilpotentChv: The coefficients ar not in indicated ring.");
              fi;

              object:=Objectify(NewType(NewFamily("NilpotentChvFamily"),
                                        IsAttributeStoringRep and
                                        IsNilpotentChv and
                                        IsAdditiveElementWithInverse and
                                        IsAdditiveElementWithZero),
                                rec());

              SetchevalleyAdj(object,sys);

              # Simplified form of the element (negative root vectors are also allowd "2*Len..")
              coeffs:=Filtered(List([1..2*Length(positiveRoots(sys))],
                                    i->[i,One(ring(sys))*Sum(List(Filtered(coeffs,
                                                                           j->j[1]=i),
                                                                  k->k[2]))]),
                               l->l[2]<>Zero(ring(sys)));

              Setcoefficients(object,coeffs);

              B:=Basis(lieAlgebra(sys));
              if coeffs<>[] then SetLieAlgebraElement(object,Sum(coeffs,i->i[2]*B[i[1]]));
              else SetLieAlgebraElement(object,Zero(lieAlgebra(sys))); fi;
              SetLieAlgebraCoeffs(object,Coefficients(B,LieAlgebraElement(object)));

#              l:=Length(PRoots(R));
#              admat:=AdjointMatrix(B,LieAlgebraElement(e));
#              SetPositiveAde(object,List([1..l],i->admat[i]{[1..l]}));

              SetName(object,Concatenation("<nilpotent element for ",
                                           type(sys),String(rank(sys)),
                                           " in characteristic ",String(Characteristic(sys)),">"));
              return object;
end);

InstallMethod(Ascend,
              "If possible embed the parameters in a polynomial ring with coefficients ring the base ring",
              [IsNilpotentChv,IsPolynomialRing],
              function(e,inel)
              local sys,
              check;

              sys:=chevalleyAdj(e);

              if not IsPolynomialRing(inel) then
                  Error("Ascend NilpotentChv: The new ring is not a polynomial ring to embed in!\n");
              elif CoefficientsRing(inel)<>ring(sys) then
                  Error("Ascend NilpotentChv: CoefficientsRing and ring(sys) do not coincide!\n");
              fi;

              return NilpotentChv(ChevalleyAdj(sys,inel),List(coefficients(e),i->[i[1],One(inel)*i[2]]));
end);

InstallMethod(Ascend,
              "If possible embed the parameters in a polynomial ring with coefficients ring the base ring",
              [IsNilpotentChv,IsChevalleyAdj],
              function(e,sys)
              local syse,
              check;

              syse:=chevalleyAdj(e);

              if not IsPolynomialRing(ring(sys)) then
                  Error("The new ring is not a polynomial ring to embed in.");
              elif CoefficientsRing(ring(sys))<>ring(syse) then
                  Error("CoefficientsRing and ring(syse) do not coincide.");
              fi;

              return NilpotentChv(sys,List(coefficients(e),i->[i[1],One(ring(sys))*i[2]]));
end);

InstallMethod(\*,
              "multiplication by scalar for nilpotent elements s*e",
              [IsRingElement,IsNilpotentChv],
              function(s,e)
              local sys;

              sys:=chevalleyAdj(e);
              if not s in ring(sys) then
                  Error("Scalar*NilpotentChv: Scalar not in ring of nilpotent!\n");
              fi;

              return NilpotentChv(sys,List(coefficients(e),i->[i[1],s*i[2]]));
end);

InstallMethod(\+,
              "addition for nilpotent elements e1+e2",
              [IsNilpotentChv,IsNilpotentChv],
              function(e1,e2)
              local sys1,sys2;

              sys1:=chevalleyAdj(e1);
              sys2:=chevalleyAdj(e2);
              if type(sys1)<>type(sys2) or
                 rank(sys1)<>rank(sys2) or
                 ring(sys1)<>ring(sys2) then
                  Error("NilpotentChv+NilpotentChv: Not in the same family!\n");
              fi;

              return NilpotentChv(sys1,Concatenation(coefficients(e1),coefficients(e2)));
end);

InstallMethod(AdditiveInverseMutable,
              "additive inverse for nilpotent elements -e1",
              [IsNilpotentChv],
              function(e)
              local sys;

              sys:=chevalleyAdj(e);

              return NilpotentChv(sys,List(coefficients(e),i->[i[1],-i[2]]));
end);

InstallMethod(ZeroMutable,
              "Zero vector of the Lie algebra",
              [IsNilpotentChv],
              function(e)
              local sys;

              return NilpotentChv(chevalleyAdj(e),[]);
end);


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

# ==========================================================================
# ColDirectSum
# ==========================================================================
Class(ColDirectSum, BaseOverlap, rec(
    #-----------------------------------------------------------------------
    abbrevs := [
        function(arg)
          arg:=Flat(arg);
          return [arg[1], arg{[2..Length(arg)]}];
        end ],
    #-----------------------------------------------------------------------
    new := meth(self, overlap, spls)
       return self._new(0, overlap, spls);
    end,
    #-----------------------------------------------------------------------
    dims := meth(self)
        local ovdim;
	ovdim := self.ovDim(self.overlap,
	                    List(self._children, t -> t.dimensions[1]));
	return [ ovdim[3] - ovdim[2] + 1, # max - min + 1
	         Sum(self._children, t -> t.dimensions[2]) ];
    end,
    #-----------------------------------------------------------------------
    toAMat := meth(self)
        return
	   AMatSPL(
	       Sparse(
		   self._coloverlap(
		       List(self._children, t -> t.dimensions[1]),
		       self.overlap
		   )
	       )
	   ) *
	   DirectSumAMat(List(self._children, AMatSPL));
    end,
));

ColDirectSum._transpose_class := RowDirectSum;
RowDirectSum._transpose_class := ColDirectSum;


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

#P Permutations
#P ------------
#P
#P Under different circumstances different objects are called permutations,
#P even in the context of linear algrebra.
#P
#P Following list describes these objects and their representation in SPIRAL:
#P
#P  1. Regular permutations
#P     (a) in cycle notation : (1,2)(3,4)
#P     (b) as lists          : [2,1,4,3]
#P     (c) as index space mapping function:  i -> i+1 mod N
#P
#P  2. Parametrized permutation classes
#P     [For example stride permutations L(size,str) | size mod str = 0]
#P     Represented as a construction function, which returns an index mapping
#P     function - (c) above.
#P
#P  Conversions: ListPerm     (b) <- (a)   [gap.perm]
#P               PermList     (a) <- (b)   [gap.perm]
#P               PermFunc     (a) <- (c)   [spiral.spl.perm]
#P               ListPermFunc (b) <- (c)   [spiral.spl.perm]
#P
#P -------------------

#F PermFunc(<func>, <size>) . . . . . convert perm. function into explicit GAP perm.
#F
#F  PermFunc converts a 0-based permutation function used in SPLs, into an explicit
#F  GAP permutation. Recall that GAP permutations are 1-based, and are in cycle
#F  representation.
#F
PermFunc := (func, size) ->
    PermList( List([0..size-1], func) + 1 );

#F PermFunc(<func>, <size>) . . . . . convert perm. function into explicit list perm.
#F
#F  PermFunc converts a 0-based permutation function used in SPLs, into an explicit
#F  1-based list permutation. List permutations can be converted to GAP permutations
#F  using PermList. Alternatively PermFunc can be used, wihch returns GAP permutation.
#F
ListPermFunc := (func, size) ->
    List([0..size-1], func) + 1;

# ==========================================================================
# FuncClass
#
# Base class for symbolic functions
# ==========================================================================
Class(FuncClass, BaseMat, Function, rec(
    #-----------------------------------------------------------------------
    # Must be implemented in subclasses
    #-----------------------------------------------------------------------
    lambda := self >> Error("not implemented"),
    domain := self >> Error("not implemented"),
    range := self >> Error("not implemented"),

    #-----------------------------------------------------------------------
    _perm := true,
    isReal := True,
    isPermutation := self >> false,
    perm := self >> Checked(self.isPermutation(), PermFunc(x->self.lambda().at(x), self.range())),

    #-----------------------------------------------------------------------
    print   := (self,i,is) >> Print(
	self.name, "(", PrintCS(self.params), ")", When(self.transposed, ".transpose()")),
    #-----------------------------------------------------------------------
    equals := (self, other) >> ObjId(other) = ObjId(self) and self.params=other.params,
    dims := self >> [self.range(), self.domain()],

    advdims   := self >> [ [[ self.range() ]], [[ self.domain() ]] ],

    # size along each dimension, for a multidimensional range
    # for compatibility with ClassSPL, we wrap this into another list
    # (list of outputs, since ClassSPL can have >1 output)
    advrange  := self >> [[ self.range() ]],

    # size along each dimension, for a multidimensional domain
    # for compatibility with ClassSPL, we wrap this into another list
    # (list of outputs, since ClassSPL can have >1 output)
    advdomain := self >> [[ self.domain() ]],

    # dimensionality of range (1-d, 2-d, etc)
    advrangeDim := self >> Length(self.advrange()[1]),
    # dimensionality of domain (1-d, 2-d, etc)
    advdomainDim := self >> Length(self.advdomain()[1]),
    #-----------------------------------------------------------------------
    toAMat := self >> Gath(self).toAMat(),
    #-----------------------------------------------------------------------
    arithmeticCost := (self, costMul, costAddMul) >> costMul(0) - costMul(0),
    #-----------------------------------------------------------------------
    transpose := self >> CopyFields(self, rec(transposed := not self.transposed )),
    #-----------------------------------------------------------------------
    conjTranspose := self >> self.transpose(),
    #-----------------------------------------------------------------------
    normalizedArithCost := self >> 0,
    #-----------------------------------------------------------------------
    free := self >> Union(List(self.params, FreeVars)),
    #-----------------------------------------------------------------------
    # Rewrite rules support
    #
    from_rChildren := (self, rch) >> CopyFields(ApplyFunc(ObjId(self), rch),
	rec(transposed:=self.transposed)),

    # NOTE: self.transposed not exposed
    rChildren := self >> self.params,

    rSetChild := meth(self, n, newChild)
        self.params[n] := newChild;
        # self.canonizeParams(); ??
	self.dimensions := self.dims();
    end,
    # ----------------------------------------------------------------------
    checkParams := meth(self, params)
        local nargs, nump;
	nargs := NumArgs(self.def);
	nump := Length(params);
	if nargs <> -1 and nargs <> nump then
            Error(self.name, " needs ", NumArgs(self.def), " parameters: ",
		ParamsFunc(self.def), "\n");
	fi;
    end,
    #-----------------------------------------------------------------------
    canonizeParams := meth(self, params)
        local A, nump;
	nump := Length(params);
	if IsBound(self.abbrevs) then
            for A in self.abbrevs do
                if NumArgs(A) = -1 or NumArgs(A) = nump then
		    return ApplyFunc(A, params);
		fi;
	    od;
            return params;
	else
            return params;
	fi;
    end,
    #-----------------------------------------------------------------------
    __call__ := meth(arg)
        local result, self, params, lkup, h;
        self := arg[1];
        params := arg{[2..Length(arg)]};
        params := self.canonizeParams(params);
        self.checkParams(params);

        h := self.hash;
        if h<>false then
            lkup := h.objLookup(self, params);
            if lkup[1] <> false then return lkup[1]; fi;
        fi;

        result := SPL(WithBases(self, rec(params := params, transposed := false)));
        result := Inherit(result, ApplyFunc(result.def, params));
	result.dimensions := result.dims();

        if h<>false then return h.objAdd(result, lkup[2]);
        else return result;
        fi;
    end,

# obsolete
#    checkInverse := self >> Checked(self.isPermutation(),
#        PermFunc(self.direct, self.range()) * PermFunc(self.inverse, self.domain())),
));

Class(PermClass, FuncClass, rec(
    isPermutation := self >> true,
    toAMat := self >> Gath(self).toAMat(),
    equals := (self, other) >> ObjId(other) = ObjId(self) and self.params=other.params
));


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

#F CondPat(<obj>, <pat1>, <result1>, ..., <patN>, <resultN>, [<resultElse>])
#F
#F CondPat is a conditional control-flow construct similar to Cond, but instead
#F of normal boolean conditions it uses patterns that are matched against its
#F first argument <obj>. CondPat returns the result that corresponds to the
#F first matched pattern. If nothing matches <resultElse> is returned.
#F
#F Note: resultElse is optional
#F
#F CondPat is implemented as a function with delayed evaluation, and will
#F only evaluate the result that corresponds to the matched patterns, all
#F other results will not be evaluated, for example:
#F
#F CondPat(mul(X,2), [mul, @, 2], Print("yes"), [add, @, 2], Print("no"));
#F
#F will only execute Print("yes") -- as expected.
#F

CondPat := UnevalArgs(function(arg)
    local usage, i, pat, obj;

	usage := "CondPat(<obj>, <pat1>, <result1>, ..., <patN>, <resultN>, <resultElse>)";

	if Length(arg) < 3 then
		Error(usage);
	fi;

    obj := Eval(arg[1]);
    i := 2;
    while i <= Length(arg)-1 do
        pat := Eval(arg[i]);
		if PatternMatch(obj, pat, empty_cx()) then
			return Eval(arg[i+1]);
		fi;
		i := i+2;
    od;
    if i=Length(arg) then
		return Eval(Last(arg));
    else
		Error("CondPat: No 'else' result, and no patterns match");
    fi;
end);


# Copyright (c) 2018-2020, Carnegie Mellon University
# See LICENSE for details

# Radix n kernel for FFTE, targeting ARM SVE

buildSVEKernel_a := function(n, kk, conf, opts)
    local vlen, name, suffix, filesuffix, t, j, k, l, m, tmp1, tmp2, tmp3,
        gi, g, gc, si, ss, s, sc, dft, rts, rt, opcnts, dfts, dftc, i, twt, twf, twl, tws, twc,
        cl, cc, c, clp, useFMA, _FMA, lp;

    name := DFTnameStr(n, kk);
    suffix := "a";
    filesuffix := "sve_";
#    suffix := "_j";
    Print("\n\n// == Generating \"", name, "\" ==================================================\n\n");

    vlen := TInt;
    useFMA := When(IsBound(conf.useFMA), conf.useFMA, true);
    _FMA := When(useFMA, FMA, p -> p);

    # variables
    t := var.fresh_t("t", TInt);
    j := var.fresh_t("j", TInt);
    k := V(0);
    l := var.fresh_t("l", TInt);
    m := V(1);
    lp := var.fresh_t("lp", TPtr(TInt));
#    mp := var.fresh_t("mp", TPtr(TInt));

    # temp arrays
    tmp1 := var.fresh_t("R", TArray(TVectSVE, 2*n));
    tmp2 := var.fresh_t("S", TArray(TVectSVE, 2*n));
    tmp3 := var.fresh_t("T", TArray(TVectSVE, 2*n));

    # gather
    gi := _i -> VGath_L(1, vlen, 2*(k + j*m + _i*l*m));
    g := RulesSums(SumsSPL(VStack(List([0..n-1], gi)), opts));
    gc := Compile(opts.codegen(g, tmp1, X, opts), opts);

    # scatter
    si := _i -> ScatH(1, 1, 2*n*j+_i, 2*n);
    ss := SumsSPL(HStack(List([0..2*n-1], si)), opts);
    s := RulesSums(SubstTopDown(ss, @(1, ScatAcc), e->Scat(@(1).val.func)));
    sc := Compile(opts.codegen(s, Y, tmp3, opts), opts);

    # generate DFT kernel
    dft := RC(DFT(n, kk));
    rts := AllRuleTrees(dft, opts);
    opcnts := List(rts, r -> [ Length(Collect(CodeRuleTree(r, opts), @(1, [add, sub, mul], e->e.t=TReal))), r]);
    rt := Minimum(opcnts)[2];
    dfts := SumsRuleTree(rt, opts);
    dftc := BlockUnroll(opts.codegen(dfts, tmp2, tmp1, opts), opts);

    # twiddles as lookup table
    i := var.fresh_t("i", 2*n);
    twt := var.fresh_t("TW", TPtr(TReal));
    twf := Lambda(i, cond(
        eq(i, 0), 1,
        eq(i, 1), 0,
        sve_gath(nth(twt, 2*((n-1)*j) + 2 * idiv(i, 2) + (imod(i, 2) - 2)).toPtr(TReal), TInt, opts.pg, 2*(n-1))
    ));

    # debug twiddles
    twl := Map(twf.tolist(), RulesStrengthReduce);
    DoForAll([0..Length(twl)-1], _i->Unparse(assign(nth(Y, _i), twl[_i+1]), CUnparser, 0, 1));
    tws := RCDiag(twf);
    twc :=BlockUnroll(unroll_cmd(opts.codegen(tws, tmp3, tmp2, opts)), opts);

    # stitch code fragments together
    cl := func(TVoid, "transform", [Y, X, twt, j, k, l, m, opts.pg],
            decl([tmp1, tmp2, tmp3], chain(gc, dftc, twc, sc)));
    cc := Compile(cl, opts);

    # fixup to push decls inside (?)
    c := func(TVoid, "transform", [Y, X, twt, j, l, opts.pg],
            decl(cc.vars, _FMA(cc.cmd.cmds[1].cmd)));

    # print Radix N FFTE kernel as function
    #PrintCode(name, c, opts);
    #PrintTo(name::".c", PrintCode(name, c, opts));

    ########################################################
    # version with j loop vectorized as m == 1, k == 0
    clp := func(TVoid, "transform", [Y, X, twt, lp],
        decl(c.cmd.vars::c.free()::[opts.pg, l],
            chain(
                assign(l, deref(lp)),
                sve_loopn(j, l,
                    c.cmd.cmd
                )
            )
        )
    );

    # print Radix N FFTE kernel as function
    PrintCode(opts.FortranIze(name::suffix), clp, opts);
    PrintTo(name::filesuffix::suffix::".c", PrintCode(opts.FortranIze(name::suffix), clp, opts));
end;


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

NewRulesFor(TICompose, rec(
    TICompose_DropTag := rec(
        applicable := t -> true,
        children := t -> [[ t.params[3] ]],
        apply := (t, C, Nonterms) -> ICompose(t.params[1], t.params[2], C[1])
    )
));

#F InitStreamSw(<radix>)
#F
#F Example:
#F   opts := InitStreamSw(2);
#F   c := CodeRuleTree(opts.transforms.DFT(64), opts);
#F   me   := CMatrix(c, opts);
#F   them := MatSPL(opts.transforms.DFT(64));
#F   InfinityNormMat(me-them);
#F
InitStreamSw := radix -> CopyFields(
    CplxSpiralDefaults,
    IntelC99Mixin, # uncomment this for C99 output (default = SSE2 intrinsics)
    rec(
    breakdownRules := rec(
        DFTDR :=  [CopyFields(DFTDR_tSPL_Pease, rec(precompute:=true)) ],
        DFT   :=  [DFT_Base, DFT_CT ],
        TTensorI := [IxA_base, IxA_L_base, L_IxA_base, AxI_base],
        TCompose := [TCompose_tag],
        TICompose := [TICompose_DropTag],
        TDiag    := [TDiag_base]
    ),

    transforms := rec(
        DFT := n -> DFTDR(n, radix, [AStream(radix)])
    ),

#    unparser := CMacroUnparserProg,

    globalUnrolling := radix
));

#F TestStreamSw(<n>, <radix>, <use_transpose>)  - software test of Pease DFT used for streaming hardware
#F
#F use_transpose = true:  DRDFT
#F use_transpose = false: DFTDR
#F
TestStreamSw := function(n, radix, use_transpose)
    local opts, t, rt, c, err, me, them;
    opts := InitStreamSw(radix);

    PrintErr("Compiling...\n");
    t := When(use_transpose, opts.transforms.DFT(n).transpose(), opts.transforms.DFT(n));
    rt := RandomRuleTree(t, opts);
    if rt = false
        then Error("No ruletrees."); fi;
    c := CodeRuleTree(rt, opts);

    PrintErr("Running and computing the code matrix...\n");
    me := CMatrix(c, opts);

    PrintErr("Comparing to the definition...\n");
    them := MatSPL(t);
    err := InfinityNormMat(me-them);

    if err < 1e-6 then PrintErr(t, " -- ", GreenStr("OK\n"));
    else PrintErr(t, " -- ", RedStr("FAIL\n")); fi;
end;

TestStreamSwPrint := function(n, radix, use_transpose)
    local opts, t, rt, c, err, me, them;
    opts := InitStreamSw(radix);

    t := When(use_transpose, opts.transforms.DFT(n).transpose(), opts.transforms.DFT(n));
    rt := RandomRuleTree(t, opts);
    if rt = false
        then Error("No ruletrees."); fi;
    c := CodeRuleTree(rt, opts);

    return PrintCode("function_name", c, opts);
#    return rt;
end;


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

_svctSizes := (N, p, v) -> Filtered([1..N], i->ForAll(Factors(i), j->j<=p) and IsInt(i/v^2));
_defaultSizes := (func, default) -> ((arg) -> When(Length(arg) = 1, func(When(IsList(arg[1]), arg[1], [arg[1]])), func(default)));

_parse := function(arglist)
    local isa, n, sizes, optrec;

    isa := "f64re";
    optrec := rec(interleavedComplex := true);

    if Length(arglist) = 0 then
        n := 1;
    elif Length(arglist) = 1 then
        if IsInt(arglist[1]) then
            n := arglist[1];
        elif IsList(arglist[1]) then
            n := arglist[1];
        else
            n := 0;
            isa := arglist[1];
        fi;
    elif Length(arglist) = 2 then
        n := arglist[1];
        isa := arglist[2];
    else
        n := arglist[1];
        isa := arglist[2];
        optrec := arglist[3];
    fi;

    sizes := When(IsList(n), n, List([1..n], i->2^i));

    return [sizes, isa, optrec];
end;

doDft := function(arg)
    local sizes, opts, dpr, isa, trafos, t, optrec, dpbench, defaults;

    [sizes, isa, optrec] := _parse(arg);

    defaults := rec(
        breakdownRules := rec(
            DFT := [ DFT_Base, DFT_PD, DFT_CT, DFT_GoodThomas, DFT_Rader ],
            WHT := [ WHT_Base, WHT_BinSplit ],
            MDDFT := [ MDDFT_Base, MDDFT_RowCol ],
            TRC := [ TRC_tag ]
        ),
        unparser := CUnparserProg
    );
    opts := CopyFields(When(optrec.generateComplexCode, CplxSpiralDefaults, SpiralDefaults), defaults, optrec);
    opts := InitDataType(opts, isa);

    if opts.generateComplexCode then
        t := i -> RC(DFT(i));
    else
        t := When(IsBound(opts.tags), i -> ComplexT(DFT(i), opts).withTags(opts.tags), i ->ComplexT(DFT(i), opts));
    fi;
    opts.benchTransforms := List(sizes, k -> t(k));

    dpr  := rec(verbosity := 0, timeBaseCases:=true);
    dpbench := spiral.libgen.DPBench(rec((Concat(isa, When(opts.interleavedComplex, "_ic", "_sc"))) := opts), dpr);
    if IsBound(optrec.verify) then dpbench.matrixVerify := optrec.verify; fi;

    return dpbench;
end;

# doDft auto-vectorization interface
#b := doDft(10, "f32re", _autovect(IntelC, rec(interleavedComplex := true, language := "c.icl.opt.core2")));
#b := doDft(16, "f32c", _autovect(IntelC, rec(interleavedComplex := true, language := "c.icl.opt.core2", generateComplexCode:=true)));

_autovect := (cc, opts) -> CopyFields(opts, rec(
    useRestrict := true,
    looppragma := cc.looppragma,
    restrict := cc.restrict,
    postalign := cc.postalign,
    arrayBufModifier := Concat("static ", cc.alignmentSpecifier()),
    arrayDataModifier := Concat("static ", cc.alignmentSpecifier()),
    compileStrategy:=NoCSE
));

benchScalar := () -> rec(
    float := rec(
        dft_ic := rec(
            medium := _defaultSizes(s->doDft(s, "f32re", rec(interleavedComplex := true)), 13)
        ),
        dft_sc := rec(
            medium := _defaultSizes(s->doDft(s, "f32re", rec(interleavedComplex := false)), 13)
        )
    ),
    double := rec(
        dft_ic := rec(
            medium := _defaultSizes(s->doDft(s, "f64re", rec(interleavedComplex := true)), 13)
        ),
        dft_sc := rec(
            medium := _defaultSizes(s->doDft(s, "f64re", rec(interleavedComplex := false)), 13)
        )
    )
);


#############################################################################
##
#W  listops.gi             automgrp package                    Yevgen Muntyan
#W                                                             Dmytro Savchuk
##
#Y  Copyright (C) 2003 - 2018 Yevgen Muntyan, Dmytro Savchuk
##

###############################################################################
##
##  AG_IsCorrectAutomatonList( <list>, <invertible> )
##
InstallGlobalFunction(AG_IsCorrectAutomatonList,
function(list, invertible)
  local len, deg, i, j, sym, semi;

  if not IsDenseList(list) then
    return false;
  fi;

  len := Length(list);
  if len = 0 then
    return false;
  fi;

  for i in [1..len] do
    if not IsDenseList(list[i]) then
      return false;
    fi;
    if Length(list[i]) <> Length(list[1]) then
      return false;
    fi;
  od;

  deg := Length(list[1]) - 1;
  if deg < 1 then
    return false;
  fi;

  sym := SymmetricGroup(deg);
  semi := FullTransformationSemigroup(deg);

  for i in [1..len] do
    for j in [1..deg] do
      if not IsInt(list[i][j]) then
        return false;
      fi;
      if list[i][j] > len or list[i][j] < 1 then
        return false;
      fi;
    od;

    if not list[i][deg + 1] in sym then
      if not list[i][deg + 1] in semi then
        return false;
      fi;
      if invertible and list[i][deg + 1]^-1=fail then
        return false;
      fi;
    fi;
  od;

  return true;
end);

###############################################################################
##
##  AG_IsCorrectRecurList( <list>, <invertible> )
##
InstallGlobalFunction(AG_IsCorrectRecurList,
function(list, invertible)
  local len, deg, i, j, k, sym, semi, inv_states;

  if not IsDenseList(list) then
    return false;
  fi;

  len := Length(list);
  if len = 0 then
    return false;
  fi;

  for i in [1..len] do
    if not IsDenseList(list[i]) then
      return false;
    fi;
    if Length(list[i]) <> Length(list[1]) then
      return false;
    fi;
  od;

  deg := Length(list[1]) - 1;
  if deg < 2 then
    return false;
  fi;

  sym := SymmetricGroup(deg);
  semi := FullTransformationSemigroup(deg);

  for i in [1..len] do
    for j in [1..deg] do
      if IsInt(list[i][j]) then
        if list[i][j] > len or list[i][j] < -len or list[i][j] = 0 then
          return false;
        fi;
      elif IsList(list[i][j]) then
        if not IsDenseList(list[i][j]) then
          return false;
        fi;
        for k in list[i][j] do
          if (not IsInt(k)) or k > len or k < -len or k = 0 then
            return false;
          fi;
        od;
      else
        return false;
      fi;
    od;

    # Check that everything is correct here
    if (not IsPerm(list[i][deg + 1])) and (not IsTransformation(list[i][deg + 1])) then
      return false;
    elif LargestMovedPoint(list[i][deg + 1]) > deg then
      return false;
    elif IsTransformation(list[i][deg + 1]) and invertible and list[i][deg + 1]^-1=fail then
      return false;
    fi;
  od;

#  check if there is x^-1 in the list, while x is not invertible
  inv_states:=[];
  for i in [1..len] do
    if AG_IsInvertibleStateInList(i,list) then Add(inv_states,i); fi;
  od;

  for i in [1..len] do
    for j in [1..deg] do
      if IsInt(list[i][j]) then
        if list[i][j]<0 and not -list[i][j] in inv_states then
          return false;
        fi;
      else
        for k in list[i][j] do
          if k<0 and not -k in inv_states then
            return false;
          fi;
        od;
      fi;
    od;
  od;

  return true;
end);

###############################################################################
##
##  AG_ConnectedStatesInList(state, list)
##
##  Returns list of states which are reachable from given state,
##  it does not check correctness of arguments
##
InstallGlobalFunction(AG_ConnectedStatesInList,
function(state, list)
  local i, j, s, d, to_check, checked;

  d := Length(list[1]) - 1;

  to_check := [state];
  checked := [];

  while Length(to_check) <> 0 do
    for s in to_check do
      for i in [1..d] do
        if IsList(list[s][i]) then
          for j in AsSet(List(list[s][i],AbsInt)) do
            if (not j in checked) and (not j in to_check) then
              to_check := Union(to_check, [j]);
            fi;
          od;
        else
          if (not AbsInt(list[s][i]) in checked) and (not AbsInt(list[s][i]) in to_check) then
            to_check := Union(to_check, [AbsInt(list[s][i])]);
          fi;
        fi;
      od;
      checked := Union(checked, [s]);
      to_check := Difference(to_check, [s]);
    od;
  od;

  return checked;
end);

###############################################################################
##
##  AG_IsTrivialStateInList( <state>, <list>)
##
##  Checks whether given state is trivial.
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_IsTrivialStateInList,
function(state, list)
  local deg;
  deg := Length(list[1]) - 1;
  # IsOne works for Transformation's
  return ForAll(AG_ConnectedStatesInList(state, list),
                s -> IsOne(list[s][deg+1]));
end);

###############################################################################
##
##  AG_IsObviouslyTrivialStateInList( <state>, <list>)
##
##  Checks whether given state is obviously trivial.
##  Works for lists generating self-similar groups.
##  Returns `true' if <state>=(*,...,*)(), where
##  * could be either +-<state> or [+-<state>], or [].
##
InstallGlobalFunction(AG_IsObviouslyTrivialStateInList,
function(state, list)
  local deg, check;

  check := function(s)
    if IsInt(s) then return state=AbsInt(s);
                else return s=[] or (Length(s)=1 and state=AbsInt(s[1]));
    fi;
  end;

  deg := Length(list[1]) - 1;
  if not IsOne(list[state][deg+1]) then return false; fi;
  # IsOne works for Transformation's
  return ForAll(list[state]{[1..deg]}, check);
end);

###############################################################################
##
##  AG_IsInvertibleStateInList( <state>, <list> )
##
##  Checks whether given state is invertible.
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_IsInvertibleStateInList,
function(state, list)
  local deg;
  deg := Length(list[1]) - 1;
  return ForAll(AG_ConnectedStatesInList(state, list),
                s -> (list[s][deg+1]^-1<>fail));
end);

###############################################################################
##
##  AG_AreEquivalentStatesInList( <state1>, <state2>, <list> )
##
##  Checks whether two given states are equivalent.
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_AreEquivalentStatesInList,
function(state1, state2, list)
  local d, checked_pairs, pos, s1, s2, np, i;

  d := Length(list[1]) - 1;
  checked_pairs := [[state1, state2]];
  pos := 0;

  while Length(checked_pairs) <> pos do
    pos := pos + 1;
    s1 := checked_pairs[pos][1];
    s2 := checked_pairs[pos][2];

    if list[s1][d+1] <> list[s2][d+1] then
      return false;
    fi;

    for i in [1..d] do
      np := [list[s1][i], list[s2][i]];
      if not np in checked_pairs then
        checked_pairs := Concatenation(checked_pairs, [np]);
      fi;
    od;
  od;

  return true;
end);

###############################################################################
##
##  AG_AreEquivalentStatesInLists( <state1>, <state2>, <list1>, <list2>)
##
##  Checks whether two given states in different lists are equivalent.
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_AreEquivalentStatesInLists,
function(state1, state2, list1, list2)
  local d, checked_pairs, pos, s1, s2, np, i;

  d := Length(list1[1]) - 1;
  checked_pairs := [[state1, state2]];
  pos := 0;

  while Length(checked_pairs) <> pos do
    pos := pos + 1;
    s1 := checked_pairs[pos][1];
    s2 := checked_pairs[pos][2];

    if list1[s1][d+1] <> list2[s2][d+1] then
      return false;
    fi;

    for i in [1..d] do
      np := [list1[s1][i], list2[s2][i]];
      if not np in checked_pairs then
        checked_pairs := Concatenation(checked_pairs, [np]);
      fi;
    od;
  od;

  return true;
end);

###############################################################################
##
##  AG_ReducedAutomatonInList( <list> )
##
##  Returns [new_list, list_of_states, old_states] where new_list is a new list
##  which represents reduced form of given automaton, i-th elmt of list_of_states
##  is the number of i-th state of new automaton in the old one.
##  old_states[i] is the number of state which corresponds to the i-th state
##  of the original automaton.
##
##  First state of returned list is always first state of given one.
##  It does not remove trivial state, so it's not really "reduced automaton",
##  it just removes equivalent states.
##  TODO: write such function which removes trivial state
##
##  Does not check correctness of list.
##
##  WARNING: do *NOT* change it.
##
InstallGlobalFunction(AG_ReducedAutomatonInList,
function(list)
  local   i, n, triv_states, equiv_classes, checked_states, s, s1, s2,
          eq_cl, eq_cl_1, eq_cl_2, are_equiv, eq_cl_reprs,
          new_states, new_list, deg,
          reduced_automaton, state, states_reprs;

  n := Length(list);
  triv_states := [];
  equiv_classes := [];
  checked_states := [];
  deg := Length(list[1]) - 1;

  for s in [1..n] do
      if AG_IsTrivialStateInList(s, list) then
          triv_states := Union(triv_states, [s]);
      fi;
  od;

  equiv_classes:=[triv_states];
  for s1 in Difference([1..n], triv_states) do
  for s2 in Difference([s1+1..n], triv_states) do
    are_equiv := AG_AreEquivalentStatesInList(s1, s2, list);

    if s1 in checked_states then
      for eq_cl in equiv_classes do
        if s1 in eq_cl then
          eq_cl_1 := StructuralCopy(eq_cl);
          break; fi; od;
    else
      equiv_classes := Union(equiv_classes, [[s1]]);
      eq_cl_1 := [s1];
      checked_states := Union(checked_states, [s1]);
    fi;
    if s2 in checked_states then
      for eq_cl in equiv_classes do
        if s2 in eq_cl then
          eq_cl_2 := StructuralCopy(eq_cl);
          break; fi; od;
    else
      equiv_classes := Union(equiv_classes, [[s2]]);
      eq_cl_2 := [s2];
      checked_states := Union(checked_states, [s2]);
    fi;

    if are_equiv then
      equiv_classes := Difference(equiv_classes, [eq_cl_1, eq_cl_2]);
      equiv_classes := Union(equiv_classes, [Union(eq_cl_1, eq_cl_2)]);
    fi;
  od;
  od;
  states_reprs := [1..n];
  for eq_cl in equiv_classes do
    for s in eq_cl do
      states_reprs[s] := Minimum(eq_cl);
    od;
  od;

  new_states := Set(states_reprs);
  new_list := [];

  for s in new_states do
    state := [];
    state[deg+1] := list[s][deg+1];
    for i in [1..deg] do
      state[i] := Position(new_states, states_reprs[list[s][i]]);
    od;
    new_list := Concatenation(new_list, [state]);
  od;

  return [new_list, new_states, List([1..n], i -> Position(new_states, states_reprs[i]))];
end);

###############################################################################
##
##  AG_MinimalSubAutomatonInlist(<states>, <list>)
##
##  Returns list representation of automaton given by <list> which is minimal
##  subatomaton of automaton containing states <states>.
##
##  Does not check correctness of list.
##
InstallGlobalFunction(AG_MinimalSubAutomatonInlist,
function(states, list)
  local s, new_states, state, new_list, i, deg;

  new_states := [];
  for s in states do
    new_states := Union(new_states, AG_ConnectedStatesInList(s, list));
  od;

  new_list := [];
  deg := Length(list[1]) - 1;

  for s in new_states do
    state := [];
    for i in [1..deg] do
      state[i] := Position(new_states, list[s][i]);
    od;
    state[deg+1] := list[s][deg+1];
    new_list := Concatenation(new_list, [state]);
  od;

  return [new_list, new_states];
end);

###############################################################################
##
##  AG_PermuteStatesInList(<list>, <perm>)
##
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_PermuteStatesInList,
function(list, perm)
  local new_list, i, j, deg;

  deg := Length(list[1]) - 1;
  new_list := [];
  for i in [1..Length(list)] do
    new_list[i^perm] := [];
    for j in [1..deg] do
      new_list[i^perm][j] := list[i][j]^perm;
    od;
    new_list[i^perm][deg+1] := list[i][deg+1];
  od;

  return new_list;
end);

###############################################################################
##
##  AG_WordStateInList(<w>, <s>, <list>, <reduce>, <trivstate>)
##
##  It's ProjectWord from selfs.g
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_WordStateInList,
function(w, s, list, reduce, trivstate)
  local i, perm, d, proj, red, reduce_word;

  reduce_word := function(v)
    local len, red, x;
    len := 0;
    red := [];
    for x in v do
      if x <> trivstate then
        if len <> 0 and x = -red[len] then
          Remove(red, len);
          len := len - 1;
        else
          Add(red, x);
          len := len + 1;
        fi;
      fi;
    od;
    return red;
  end;

  d := Length(list[1])-1;
  proj := [];
  perm := ();
  for i in [1..Length(w)] do
    Add(proj, list[w[i]][s^perm]);
    perm := perm * list[w[i]][d+1];
  od;
  if reduce then
    return reduce_word(proj);
  else
    return proj;
  fi;
end);

###############################################################################
##
##  AG_WordStateAndPermInList(<w>, <s>, <list>)
##
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_WordStateAndPermInList,
function(w, s, list)
  local i, perm, perm_res, new_state, d, proj;
  d := Length(list[1])-1;
  proj := [];
  perm := ();
  perm_res := ();
  for i in [1..Length(w)] do
    new_state := list[w[i]][s^perm];
    Add(proj, new_state);
    perm := perm * list[w[i]][d+1];
    perm_res := perm_res * list[new_state][d+1];
  od;
  return [proj, perm_res];
end);

###############################################################################
##
##  AG_ImageOfVertexInList(<list>, <init>, <vertex>)
##
##  Does not check correctness of arguments.
##
InstallGlobalFunction(AG_ImageOfVertexInList,
function(list, s, seq)
  local deg, img, x;

  deg := Length(list[1]) - 1;
  img := [];
  for x in seq do
      Add(img, x^list[s][deg+1]);
      s := list[s][x];
  od;

  return img;
end);

###############################################################################
##
##  AG_DiagonalPowerInList(<list>, <n>)
##
InstallGlobalFunction(AG_DiagonalPowerInList,
function(list, n, names)
  local d, nlist, nd, nalph, nstates, nperm,
        i, j, k, letter, n_letter, n_state, state,
        nnames;

  d := Length(list[1]) - 1;
  nd := d ^ n;
  nalph := Tuples([1..d], n);
  nstates := Tuples([1..Length(list)], n);
  nlist := List([1..Length(nstates)], i -> []);
  nnames := List(nstates, s->List(s, i->names[i]));

  for i in [1..Length(nlist)] do
    nperm := [];
    state := nstates[i];
    for j in [1..nd] do
      letter := nalph[j];
      n_letter := [];
      n_state := [];
      for k in [1..n] do
        n_letter[k] := letter[k]^list[state[k]][d+1];
        n_state[k] := list[state[k]][letter[k]];
      od;
      nperm[j] := n_letter;
      nlist[i][j] := Position(nstates, n_state);
    od;
    nlist[i][nd+1] := PermListList(nalph, nperm);
  od;

  nnames := List(nnames, l->Concatenation(l));
  return [nlist, nnames];
end);

###############################################################################
##
##  AG_MultAlphabetInList(<list>, <n>)
##
InstallGlobalFunction(AG_MultAlphabetInList,
function(list, n)
  local d, nlist, nd, nalph, nperm,
        i, j, k, letter, n_letter, st;

  d := Length(list[1]) - 1;
  nd := d ^ n;
  nalph := Tuples([1..d], n);
  nlist := List(list, i -> []);

  for i in [1..Length(nlist)] do
    nperm := [];
    for j in [1..Length(nalph)] do
      letter := nalph[j];
      n_letter := [];
      st := i;
      for k in [1..n] do
        Add(n_letter, letter[k]^list[st][d+1]);
        st := list[st][letter[k]];
      od;
      nlist[i][j] := st;
      nperm[j] := n_letter;
    od;
    nlist[i][nd+1] := PermListList(nalph, nperm);
  od;

  return nlist;
end);

###############################################################################
##
##  AG_HasDualInList(<list>)
##
InstallGlobalFunction(AG_HasDualInList,
function(list)
  local i, j, p, d, n;
  d := Length(list[1]) - 1;
  n := Length(list);
  for i in [1..d] do
    p := [];
    for j in [1..n] do
      p[j] := list[j][i];
    od;
    if PermListList([1..n], p) = fail then
      return false;
    fi;
  od;
  return true;
end);

###############################################################################
##
##  AG_DualAutomatonList(<list>)
##
InstallGlobalFunction(AG_DualAutomatonList,
function(list)
  local dual, d, n;
  d := Length(list[1]) - 1;
  n := Length(list);
  return List([1..d], i -> Concatenation(List([1..n], j -> i^list[j][d+1]),
    [PermList(List([1..n], j -> list[j][i]))]));
end);

###############################################################################
##
##  AG_HasDualOfInverseInList(<list>)
##
InstallGlobalFunction(AG_HasDualOfInverseInList,
function(list)
  return AG_HasDualInList(AG_InverseAutomatonList(list));
end);

###############################################################################
##
##  AG_InverseAutomatonList(<list>)
##
InstallGlobalFunction(AG_InverseAutomatonList,
function(list)
  local inv, d, i;
  d := Length(list[1]) - 1;
  inv := List(list, l -> Permuted(l, l[d+1]));
  for i in [1..Length(list)] do inv[i][d+1] := inv[i][d+1]^-1; od;
  return inv;
end);

#E


local reps,tori,labels,I,II,ords,blocks;

#......Ross
reps:=[[[1,1],[2,1],[3,1],[4,1],[5,1]],    # D_5
       [[5,1],[4,1],[9,1],[7,1],[1,1]]     # D_5(a_1)
       ];

I:=[[],[]];

#
# The parabolic where the representative is distinguished
#
II:=[[],
     []];

labels:=["D_5",
         "D_5(a_1)"
         ];

tori:=[
       [],
       []
    ];

blocks:=[];

ords:=[];

return [reps,tori,labels,I,II,ords,blocks];


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

Declare(TDCT2, TDCT3, TDCT4);

_DCT_CONST := 2.5;

#######################################################################################
#   tSPL DCT rules

#    PDCT4_CT_SPL := rec(
#    isApplicable     := P -> P > 2 and ForAny(DivisorPairs(2*P), d->IsEvenInt(d[1]) and IsEvenInt(d[2])),
#    forTransposition := false,
#
#    allChildren  := P -> let(N := P,
#        Map2(Filtered(DivisorPairs(2*N), d -> IsEvenInt(d[1]) and IsEvenInt(d[2])),
#        (m,n) -> [ PRDFT3(m,-1).transpose(), PRDFT3(n) ])),
#
#    rule := (P,C) -> let(N := P, m := Rows(C[1]), n := Cols(C[2]),
#        j := Ind(n/2),
#        T := Diag(Twid(2*N, m/2, 1, 1/2, 1/2, j)),
#
#        Prm(Refl(N, 2*N-1, N, L(m*n, m))) *
#        IterDirectSum(j, j.range, Diag(BHN(m)) * C[1] * RC(T)) *
#        RC(L(m*n/4, n/2)) *
#        Tensor(I(m/2), C[2] * Diag(BHN(n))) *
#        Prm(Refl(N, 2*N-1, N, L(m*n, m)))
#        )
#    )
#

#F TDCT4(<size>, tags) - Discrete Cosine Transform, Type IV, non-terminal
#F Definition: (n x n)-matrix  [ cos((k+1/2)*(l+1/2)*pi/n) | k,l = 0...n-1 ]
#F Note:       DCT4 is symmetric
#F Example:    DCT4(8)
Class(TDCT4, TaggedNonTerminal, rec(
    abbrevs := [ N -> Checked(IsInt(N), N >= 1, [N]) ] ,
    dims := self >> [self.params[1], self.params[1]],
    isReal := True,
    terminate := self >> Mat(DCT_IVunscaled(self.params[1])),
    transpose := self >> Copy(self),
    SmallRandom := () -> Random([2, 3, 4, 6, 8, 9, 12, 16, 18, 24, 27, 32]),
    normalizedArithCost := (self) >> let(n := self.params[1], IntDouble(_DCT_CONST * n * d_log(n) / d_log(2)))
));

NewRulesFor(TDCT4, rec(
#    DCT4_CT_tSPL := rec(
#        switch := false,
#
#        applicable := (self, t) >> let(P:=t.params, P[1] > 2 and ForAny(DivisorPairs(2*P[1]), d->IsEvenInt(d[1]) and IsEvenInt(d[2])) and HasTags(t)),
#
#        children := (self, t) >> let(tags := GetTags(t), N := t.params[1],
#                                    Map2(Filtered(DivisorPairs(2*N), d -> IsEvenInt(d[1]) and IsEvenInt(d[2])),
#                                        (m,n) -> [
#                                            SetTag(TCompose([
#                                                TScat(Refl(N, 2*N-1, N, L(m*n, m))),
#                                                TTensorI(Diag(BHN(m)) * PRDFT3(m,-1).transpose(), n/2, APar, APar),
#                                                TRC(TDiag(fPrecompute(
#                                                    fCompose(dOmega(8 * N, 1), diagTensor(dLin(N/m, 2, 1, TInt), dLin(m/2, 2, 1, TInt)))
#                                                ))),
#                                                TRC(TL(m*n/4, n/2, 1, 1)),
#                                                TTensorI(PRDFT3(n) * Diag(BHN(n)), m/2, APar, APar),
#                                                TGath(Refl(N, 2*N-1, N, L(m*n, m))) ]),
#                                                tags)
#                                        ])),
#
#        apply := (self, t, C, Nonterms) >> C[1]
#    ),
#
#    # Variant better suited for vectorization
#    PDCT4_CT_SPL_Vec := rec(
#    isApplicable     := P -> P > 2 and (P mod 2) = 0,
#    forTransposition := false,
#
#    allChildren  := P -> let(N := P,
#        Map2(Filtered(DivisorPairs(2*N), d -> IsEvenInt(d[1]) and IsEvenInt(d[2])),
#        (m,n) -> [ PRDFT3(m,-1).transpose(), PRDFT3(n) ])),
#
#    rule := (P,C) -> let(N := P, m := Rows(C[1]), n := Cols(C[2]),
#            TT := Diag(fPrecompute(fCompose(dOmega(8 * N, 1),
#                        diagTensor(dLin(N/m, 2, 1, TInt), dLin(m/2, 2, 1, TInt))))),
#
#            IJ(N, n/2) *
#            L(N, m) *
#        Tensor(I(n/2),
#                   M(m,m/2) * Diag(BHN(m)) * C[1]) *
#            RC(TT) *
#            L(N, n/2) *
#        Tensor(I(m/2),
#                   L(n, 2) * C[2] * Diag(BHN(n)) * K(n, 2)) *
#            L(N, m/2) *
#            IJ(N, m/2)
#    )
#    )

    DCT4_CT_tSPL := rec(
        switch := false,

        applicable := (self, t) >> let(
            P:=t.params,
            P[1] > 2
            and ForAny(DivisorPairs(2*P[1]), d ->
                IsEvenInt(d[1]) and IsEvenInt(d[2])
            )
            and t.hasTags()
        ),

        children := (self, t) >> let(
            tags := t.getTags(),
            N := t.params[1],
            Map2(
                Filtered(DivisorPairs(2*N), d -> IsEvenInt(d[1]) and IsEvenInt(d[2])),
                (m,n) -> List([
                    TPrm(IJ(N, n/2)),
                    TTensorI(condM(m,m/2) * Diag(BHN(m)) * PRDFT3(m,-1).transpose(), n/2, AVec, AVec),
                    TTensorI(L(n, 2) * PRDFT3(n) * Diag(BHN(n)) * condK(n, 2), m/2, APar, AVec),
                    TPrm(IJ(N, m/2))
                ], i -> i.setTags(tags))
            )
        ),

        apply := (self, t, C, Nonterms) >> let(
            N:=t.params[1],
            n:=2*Rows(Nonterms[1].params[1].params[2]),
            m:=2*Rows(Nonterms[4].params[1].params[2]),
            Grp(
                C[1] * C[2]
                * ConjDiag(RC(Diag(fPrecompute(
                    fCompose(dOmega(8 * N, 1), diagTensor(dLin(N/m, 2, 1, TInt), dLin(m/2, 2, 1, TInt)))
                ))), L(N, m), L(N, n/2))
            )
            * C[3] * C[4]
        )
    )
));

#F TDCT2(<n>) - Discrete Cosine Transform, Type II, non-terminal
#F Definition: (n x n)-matrix [ cos(k*(l+1/2)*pi/n) | k,l = 0...n-1 ]
#F Note:       DCT2 is the transpose of DCT3
#F Example:    DCT2(8)
Class(TDCT2, TaggedNonTerminal, rec(
    abbrevs := [ N -> Checked(IsInt(N), N >= 1, [N]) ] ,
    dims := self >> [self.params[1], self.params[1]],
    isReal := True,
    terminate := self >> Mat(DCT_IIunscaled(self.params[1])),
    transpose := self >> TDCT3(self.params[1]),
    SmallRandom := () -> Random([2,3,4,5,6,8,9,10,12,15,16,18,24,27,30,32]),
    normalizedArithCost := (self) >> let(n := self.params[1], IntDouble(_DCT_CONST * n * d_log(n) / d_log(2)))
));

NewRulesFor(TDCT2, rec(
    DCT2_DCT4_tSPL := rec(
        switch := false,
        applicable := (self, t) >> true,
        children := (self, t) >> [[ TS(t.params[1]).withTags(t.getTags()), TDCT4(t.params[1]).withTags(t.getTags()) ]],
        apply := (self, t, C, Nonterms) >> let(P := t.params[1],
            Diag(Concat([V(2.0)], List([1..P-1], i->V(1.0)))) *
            C[1].transpose() * C[2] *
            Diag(List([0..P - 1], i -> 1/(2 * CosPi((2*i + 1)/(4*P))))))
    ))
);

#F TDCT3(<n>) - Discrete Cosine Transform, Type III, non-terminal (unscaled)
#F Definition: (n x n)-matrix [ cos((k+1/2)*l*pi/n) | k,l = 0...n-1 ]
#F  [scaled]   (n x n)-matrix [ a_l*cos((k+1/2)*l*pi/n) | k,l = 0...n-1 ]
#F                              a_j = 1/sqrt(2) for j = 0 and = 1 else
#F Note:       DCT3 is the transpose of DCT2, scaled NOT supported yet
#F Example:    DCT3(8)
Class(TDCT3, TaggedNonTerminal, rec(
    abbrevs := [ N -> Checked(IsInt(N), N >= 1, [N]) ] ,
    dims := self >> [self.params[1], self.params[1]],
    isReal := True,
    terminate := self >> Mat(DCT_IIIunscaled(self.params[1])),
    transpose := self >> TDCT2(self.params[1]),
    SmallRandom := () -> Random([2,3,4,5,6,8,9,10,12,15,16,18,24,27,30,32]),
    normalizedArithCost := (self) >> let(n := self.params[1], IntDouble(_DCT_CONST * n * d_log(n) / d_log(2)))
));

NewRulesFor(TDCT3, rec(
    DCT3_DCT2_tSPL := rec(
        switch := false,
        applicable := (self, t) >> true,
        children := (self, t) >> [[ TDCT2(t.params[1]).withTags(t.getTags()) ]],
        apply := (self, t, C, Nonterms) >> C[1].transpose()
    ),
    DCT3_DCT4_tSPL := rec(
        switch := false,
        applicable := (self, t) >> true,
        children := (self, t) >> [[ TDCT4(t.params[1]).withTags(t.getTags()), TS(t.params[1]).withTags(t.getTags()) ]],
        apply := (self, t, C, Nonterms) >> let(P := t.params[1],
            Diag(List([0..P - 1], i -> 1/(2 * CosPi((2*i + 1)/(4*P))))) *
            C[1] * C[2] *
            Diag(Concat([V(2.0)], List([1..P-1], i->V(1.0)))))
    )
));


#
# ---------- Constructors ----------
#

InstallMethod(UnipotentMod,
              "Unipotent element in simple adjoint type",
              [IsChevalleyAdj,IsList,IsList,IsInt,IsPosInt],
              function(sys,coeffs,ordering,order,root)
              local object;

              if Filtered(coeffs,i->not i[2] in Integers)=[] then coeffs:=List(coeffs,i->[i[1],One(ring(sys))*i[2]]);
              elif Filtered(coeffs,i->not i[2] in ring(sys))<>[] then Error("The coefficients ar not in indicated ring."); fi;

              object:=Objectify(NewType(NewFamily("UnipotentFamily"),
                                   IsAttributeStoringRep and
                                   IsUnipotent and
                                   IsMultiplicativeElementWithInverse),
                           rec());

              SetchevalleyAdj(object,sys);
              SetOrdering(object,ordering);

              Setcoefficients(object,CanonicMod(sys,coeffs,ordering,root));
#             SetInverse(object,InverseOp(object)); This is set at the first call Inverse(obj);

              if order = -1 and Characteristic(sys)>0 then
                  SetOrder(object,OrderOp(object));
              elif order > -1 then
                  SetOrder(object,order);
              fi;

              SetName(object,Concatenation("<unipotent element for ",
                                      type(sys),String(rank(sys)),
                                      " in characteristic ",String(Characteristic(sys)),">"));
              return object;
end);

InstallMethod(UnipotentMod,
              "Unipotent element in simple adjoint type",
              [IsChevalleyAdj,IsList,IsList,IsPosInt],
              function(sys,coeffs,ordering,root)

              return UnipotentMod(sys,coeffs,ordering,-2,root);
end);

#
# ---------- Canonic form of element modulo some roots ----------
#

InstallMethod(CanonicMod,
              "Canonic form of coefficients of unipotent element in given ordering modulo some roots",
              [IsChevalleyAdj,IsList,IsList,IsPosInt],
              function(sys,coeffs,Ordering,root)
              local lista,
              pr,Cijrs,
              flag,i,temp,suma,param,cc,k,pos;

              lista:=[];
              for i in coeffs do
                  if Ordering[i[1]]<=Ordering[root] then
                      Add(lista,ShallowCopy(i));
                  fi;
              od;

              pr:=positiveRoots(sys);

              Cijrs:=C(sys);
              flag:=true;
              while flag do
                  flag:=false;
                  i:=Length(lista);
                  while 0 < i do
                            if lista[i][2] = Zero(ring(sys)) then
                                Remove(lista,i);
                                flag:=true;
                            elif 1 < i and lista[i][1] = lista[i-1][1] then
                                lista[i-1][2]:=lista[i-1][2]+lista[i][2];
                                Remove(lista,i);
                                flag:=true;
                            elif 1 < i and Ordering[lista[i][1]] < Ordering[lista[i-1][1]] then
                                temp:=lista[i]; lista[i]:=lista[i-1]; lista[i-1]:=temp;

                                #aici r si s sunt pe pozitia i-1 si repsectiv i
                                k:=1;
                                for cc in Cijrs[lista[i-1][1]][lista[i][1]] do
                                    suma:=cc[1]*pr[lista[i-1][1]]+cc[2]*pr[lista[i][1]];
                                    pos:=Position(pr,suma);
                                    if Ordering[pos]<=Ordering[root] then
                                        param:=cc[3]*((-1)^cc[1])*(lista[i-1][2]^cc[1])*(lista[i][2]^cc[2]);
                                        Add(lista,[pos,param],i+k);
                                        k:=k+1;
                                    fi;
                                od;
                                flag:=true;
                            fi;
                            i:=i-1;
                  od;
              od;

              return Immutable(lista);
end);

#
# ---------- Arithmetic Operations modulo some roots ----------
#

InstallMethod(MMod,
              "Multiplication for unipotent elements a*b modulo some roots and with the ordering of b",
              [IsUnipotent,IsUnipotent,IsPosInt],
              function(u1,u2,root)
              local sys1,sys2,
              generic,result,coeffs1,coeffs2,
              APR,avars,pr_len,
              i;

              sys1:=chevalleyAdj(u1);
              sys2:=chevalleyAdj(u2);
              if type(sys1)<>type(sys2) or
                 rank(sys1)<>rank(sys2) or
                 ring(sys1)<>ring(sys2) then Error("Not in the same family."); fi;

              return UnipotentMod(sys2,Concatenation(coefficients(u1),coefficients(u2)),Ordering(u2));
end);

InstallMethod(CommMod,
              "Commutator for unipotent elements a^-1b^-1ab with the ordering of b",
              [IsUnipotent,IsUnipotent,IsPosInt],
              function(u1,u2,root)
              local sys1,sys2;

              sys1:=chevalleyAdj(u1);
              sys2:=chevalleyAdj(u2);
              if type(sys1)<>type(sys2) or
                 rank(sys1)<>rank(sys2) or
                 ring(sys1)<>ring(sys2) then Error("Not in the same family."); fi;
              return UnipotentMod(sys2,
                                  Concatenation(coefficients(u1^-1),coefficients(u2^-1),
                                                   coefficients(u1),coefficients(u2)),
                                  Ordering(u2),
                                  root);
end);

InstallMethod(ConjMod,
              "Conjugation for unipotent elements b^-1ab with the ordering of b",
              [IsUnipotent,IsUnipotent,IsPosInt],
              function(u1,u2,root)
              local sys1,sys2;

              sys1:=chevalleyAdj(u1);
              sys2:=chevalleyAdj(u2);
              if type(sys1)<>type(sys2) or
                 rank(sys1)<>rank(sys2) or
                 ring(sys1)<>ring(sys2) then Error("Not in the same family."); fi;
              return Unipotent(sys2,
                               Concatenation(coefficients(u2^-1),coefficients(u1),coefficients(u2)),
                               Ordering(u2),
                               root);
end);


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

# ==========================================================================
# Conjugate(<spl>, <perm>)
#    perm^-1 * spl * perm
# ==========================================================================
Class(Conjugate, BaseOperation, rec(
    new := (self, spl, conj) >> Checked(IsSPL(spl), IsSPL(conj),
	When(Dimensions(conj) = [1,1], spl,
	     SPL(WithBases( self,
		     rec( _children := [spl, conj],
			 dimensions := spl.dimensions ))))),
    #-----------------------------------------------------------------------
    isPermutation := False,
    #-----------------------------------------------------------------------
    dims := self >> self.dimensions,
    #-----------------------------------------------------------------------
    toAMat := self >>
        ConjugateAMat(AMatSPL(self._children[1]), AMatSPL(self._children[2])),
    #-----------------------------------------------------------------------
    print := (self, i, is) >>
        Print("(", self.child(1).print(i, is), ") ^ (", self.child(2).print(i+is, is), ")",
              self.printA()),
    #-----------------------------------------------------------------------
    transpose := self >>
       Inherit(self, rec(_children := [ TransposedSPL(self._children[1]),
					self._children[2] ],
	                dimensions := Reversed(self.dimensions))),
    #-----------------------------------------------------------------------
    arithmeticCost := (self, costMul, costAddMul) >>
        self._children[1].arithmeticCost(costMul, costAddMul)
));


ITER_POLY_WARN:=false;
Read(Filename(home_dir,"lib/io.gi"));
Read(Filename(home_dir,"handle.gi"));

sys:=ChevalleyAdj("E",8,GF(5));
alg:=AlgebraicU(sys);
orbs:=UnipotentClasses(alg,"Mix");

labels:=List(AllClasses(orbs),x->Label(x));


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

setLeft := function(tags)
   local i, retval;
   retval := Copy(tags);
   for i in retval do
      i.isLeftChild := true;
      i.isRightChild := false;
 od;
 return(retval);
end;

setRight := function(tags)
   local i, retval;
   retval := Copy(tags);
   for i in retval do
      i.isLeftChild := false;
      i.isRightChild := true;
 od;
 return(retval);
end;

NewRulesFor(WHT, rec(
    WHT_GT := rec(
    switch := false,
    maxSize := false,
    minSize := false,
    codeletSize := false,
    inplace := false,

    applicable := (self, t) >> let(n := Rows(t),
        n > 2 and
        (self.maxSize=false or n <= self.maxSize) and
        (self.minSize=false or n >= self.minSize) and
        not IsPrime(n)),

        children := (self, t) >> Map2(Filtered(DivisorPairs(Rows(t)), d->When(IsInt(self.codeletSize), d[1]<=self.codeletSize, true)),
        (m,n) -> [
        GT(WHT(LogInt(m, 2)), XChain([0, 1]), XChain([0, 1]), [n]).withTags(t.getTags()),
        GT(WHT(LogInt(n, 2)), XChain([1, 0]), XChain([1, 0]), [m]).withTags(t.getTags()),
        #GT(WHT(LogInt(m, 2)), XChain([0, 1]), XChain([0, 1]), [n]).withTags( let(ts := t.getTags(),  setLeft(ts)) ),
        #GT(WHT(LogInt(n, 2)), XChain([1, 0]), XChain([1, 0]), [m]).withTags( let(ts := t.getTags(), setRight(ts)) ),
        ]),

    apply := (self, t, C, Nonterms) >>  C[1] * C[2]
    )
));

NewRulesFor(DFT, rec(
    DFT_GT_CT := rec(
    switch := false,
    maxSize := false,
    minSize := 2,
    codeletSize := false,
    inplace := false,

    a := rec(
        precompute := true
    ),

    applicable := (self, t) >> let(n := Rows(t),
        n > 2 and
        (self.maxSize=false or n <= self.maxSize) and
        (self.minSize=false or n >= self.minSize) and
        not IsPrime(n)),

        #GT(DFT(m, t.params[2] mod m), XChain([0, 1]), XChain([0, 1]), [n]).withTags(  let(a := t.getTags(),  ),
        #GT(DFT(n, t.params[2] mod n), XChain([0, 1]), XChain([1, 0]), [m]).withTags(  t.getTags())

        #GT(DFT(m, t.params[2] mod m), XChain([0, 1]), XChain([0, 1]), [n]).withTags(t.getTags()),
        #GT(DFT(n, t.params[2] mod n), XChain([0, 1]), XChain([1, 0]), [m]).withTags(t.getTags())

    children := (self, t) >> Map2(Filtered(DivisorPairs(Rows(t)), d->When(IsInt(self.codeletSize), d[1]<=self.codeletSize, true)),
        (m,n) -> [
        GT(DFT(m, t.params[2] mod m), XChain([0, 1]), XChain([0, 1]), [n]).withTags(t.getTags()),
        GT(DFT(n, t.params[2] mod n), XChain([0, 1]), XChain([1, 0]), [m]).withTags(t.getTags()),
        #GT(DFT(m, t.params[2] mod m), XChain([0, 1]), XChain([0, 1]), [n]).withTags( let(ts := t.getTags(),  setLeft(ts)) ),
        #GT(DFT(n, t.params[2] mod n), XChain([0, 1]), XChain([1, 0]), [m]).withTags( let(ts := t.getTags(), setRight(ts)) ),
         ]),

    apply := (self, t, C, Nonterms) >>  let(
            inplace := When(self.inplace, Inplace, Grp),
            n := Rows(Nonterms[2].params[1]),
            rot := t.params[2],
            compute := When(self.a.precompute, fPrecompute, fComputeOnline),

            inplace(Buf(C[1] * Diag(compute(Tw1(Rows(t), n, rot))))) * C[2])
    )
));

# GT(DFT(), ...) rules
#
NewRulesFor(GT, rec(
    GT_DFT_Base2 := rec(
    applicable := (self, t) >> let(rank := Length(t.params[4]),
        rank = 0 and PatternMatch(t, [GT, DFT, @, @, @, @, @], empty_cx()) and Rows(t.params[1])=2),
    apply := (t, C, Nonterms) -> F(2)
    ),

    GT_DFT_CT := rec(
        maxSize       := false,
        minSize       := false,
        minRank       := 0,
        maxRank       := 1,
        codeletSize := 32,
        inplace := true,

        a := rec(
            precompute := true
        ),

        applicable := (self, t) >> let(
            rank := Length(t.params[4]),
            dft := t.params[1],

            rank >= self.minRank
            and rank <= self.maxRank
            and When(rank>0, t.getTags()=[], true)
            and (self.maxSize=false or Rows(dft) <= self.maxSize)
            and (self.minSize=false or Rows(dft) >= self.minSize)
            and PatternMatch(t, [GT, DFT, XChain, XChain, @, @, @], empty_cx())
            and DFT_GT_CT.applicable(dft)
        ),

        children := (self, t) >> let(
            dft := t.params[1],
            g := t.params[2], s := t.params[3],
            rot := dft.params[2],
            loop_dims := t.params[4],
            nloops := Length(loop_dims),
            tags := t.getTags(),

            Map2( Filtered(DivisorPairs(Rows(dft)), d->d[1]<=self.codeletSize),
                (m,n) -> [
                    GT(DFT(m, rot mod m),
                        s.composeWith(XChain([0, 1])), s.composeWith(XChain([0, 1])),
                        Concatenation([n], loop_dims)
                    ).withTags(tags),

                    GT(DFT(n, rot mod n),
                        g.composeWith(XChain([0, 1])), s.composeWith(XChain([1, 0])),
                        Concatenation([m], loop_dims)
                    ).withTags(tags)
                ]
            )
        ),

        apply := (self, t, C, Nonterms) >> let(
            loop_dims := t.params[4],
            s := t.params[3],
            N := Rows(t.params[1]),
            k := t.params[1].params[2],
            n := Rows(Nonterms[2].params[1]),

            inplace := When(self.inplace, Inplace, Grp),
            compute := When(self.a.precompute, fPrecompute, fComputeOnline),

            inplace(Buf(C[1] * Diag(compute(s.toDiag(loop_dims, Tw1(N,n,k)))))) * C[2]
        )
    )
));


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

IsIntSym     := x -> (IsSymbolic(x) and IsOrdT(x.t)) or (IsValue(x) and IsOrdT(x.t)) or IsInt(x);
IsPosIntSym  := x -> (IsSymbolic(x) and IsOrdT(x.t)) or (IsValue(x) and IsOrdT(x.t) and x.v > 0)  or (IsInt(x) and x > 0);
IsPosInt0Sym := x -> (IsSymbolic(x) and IsOrdT(x.t)) or (IsValue(x) and IsOrdT(x.t) and x.v >= 0) or (IsInt(x) and x >= 0);
IsRatSym     := x -> (IsSymbolic(x) and (IsOrdT(x.t) or IsRealT(x.t) or x.t=TUnknown)) or IsRat(x) or (IsValue(x) and (IsOrdT(x.t) or IsRealT(x.t)));
IsBoolSym    := x -> x.t=TBool and (IsSymbolic(x) or IsValue(x));

AnySyms := arg -> ForAny(arg, IsSymbolic);

_ev := x->When(IsRec(x) and IsBound(x.ev), x.ev(), x);

# in functions below
# vec elements could be non-values (i.e. symbolic == expressions)

isValueZero := e ->
    (e.v=0 or IsDouble(e.v) and AbsFloat(e.v)<TReal.cutoff) or
    (IsVecT(e.t) and ForAll(e.v, x->not IsSymbolic(x) and AbsFloat(_ev(x)) < TDouble.cutoff));

isValueOne := e ->
    (e.v=1 or IsDouble(e.v) and AbsFloat(e.v-1)<TReal.cutoff) or
    (IsVecT(e.t) and ForAll(e.v, x->not IsSymbolic(x) and AbsFloat(_ev(x)-1) < TDouble.cutoff));

isValueNegOne := e ->
    (e.v=-1 or IsDouble(e.v) and AbsFloat(e.v+1)<TReal.cutoff) or
    (IsVecT(e.t) and ForAll(e.v, x->not IsSymbolic(x) and AbsFloat(_ev(x)+1) < TReal.cutoff));

_is0none := e -> Cond(IsValue(e), isValueZero(e), ObjId(e)=noneExp or e=0);
_is1 := e -> Cond(IsValue(e), isValueOne(e),  e=1);

# in the patterns below 'e' could be a plan GAP integer or other non-class GAP object
_0    := @(0).cond(e -> Cond(IsValue(e), isValueZero(e), e=0));
_0none:= @(0).cond(_is0none);
_1    := @(0).cond(_is1);
_2    := @(0).cond(x -> x=2);
_neg1 := @(0).cond(e -> Cond(IsValue(e), isValueNegOne(e), e=-1));

_v0    := @(0, Value, isValueZero);
_v0none:= @(0, [Value, noneExp], e -> Cond(ObjId(e)=noneExp, true, isValueZero(e)));
_v1    := @(0, Value, isValueOne);
_vneg1 := @(0, Value, isValueNegOne);

_vtrivial := @(0, Value, e->isValueZero(e) or isValueOne(e) or isValueNegOne(e));

_vtrue    := @(0, Value, e -> e.v=true);
_vfalse    := @(0, Value, e -> e.v=false);

Declare(_divides_rec);

_divides_rec := (d,n) -> Cond(
    ObjId(n) = add and ForAll( n.args, e -> _divides_rec(d, e) ), true,
    ObjId(n) = mul and ForAny( n.args, e -> _divides_rec(d, e) ), true,
    IsSymbolic(d) or IsSymbolic(n), d=n or n=0,
    (EvalScalar(n) mod EvalScalar(d)) = 0);

_divides := (d,n) -> CondPat( d,
    [mul, param, param, ...], # product of distinct params
        ForAll( d.args, p -> IsParam(p) and _divides_rec(p,n) ) and Length(Set(d.args))=Length(d.args),
    [mul, Value, param],
        _divides_rec(d.args[1], n) and _divides_rec(d.args[2], n),
    # else
    _divides_rec(d,n));

_isEven := x -> _divides(2, x);

# We can't know for sure that d|n, because _dividesUnsafe returns true if either d or n is a variable
_dividesUnsafe := (d,n) -> Cond(IsSymbolic(d) or IsSymbolic(n), true,
    (EvalScalar(n) mod EvalScalar(d)) = 0);

_unwrap := n -> Cond(IsValue(n), n.v, n);
Declare(_stripval);
_stripval := n -> Cond( IsValue(n), _stripval(n.v),
                        IsList(n),  List(n, _stripval),
                        n );

_unwrapV := (n) -> let(_n := EvalScalar(n), Cond(IsValue(_n), n.v, _n));


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

# DSCirculant(<n>, <P>, <v>, <ds>, <ofs>)
#    Downsampled circulant
#
Class(DSCirculant, Circulant, rec(
    abbrevs := [ (n, P, v, ds, ofs) ->
	    [ Checked(IsPosInt(n), n),
	      toFunc(P),
	      Checked(IsInt(v), v),
	      Checked(IsPosInt(ds), ds),
	      Checked(IsPosInt0(ofs), ofs < ds, ofs) ]
    ],

    dims := self >> let(p:=self.params, n:=p[1], ds:=p[4], ofs:=p[5],
	[ FloorRat((n+ofs)/ds), n ]),

    terminate := self >> let(cterminate := Circulant.terminate,
        DownSample(self.params[1], self.params[4], self.params[5]) *
        cterminate(self)),

    transpose := NonTerminal.transpose,

    HashId := self >> let(p := self.params, [p[4], p[1], p[2].domain(), p[3]])
));

# since Circulant is already added, DSCirculant won't be added automatically
AddNonTerminal(DSCirculant);

##
## Rules
##
RulesFor(DSCirculant, rec(
    ###################################################################
    ## Time domain methods
    ###################################################################

    #F RuleCirculant_Base: (base case)
    #F
    DSCirculant_Base := rec (
	info             := "DSCirculant -> Mat",
	forTransposition := false,
	isApplicable     := P -> P[1] <= d_sqrt(SPL_DEFAULTS.globalUnrolling),
	allChildren      := P -> [[ ]],
	rule := (P, C) -> ApplyFunc(DSCirculant, P).terminate()
    ),

    DSCirculant_toFilt := rec (
	info             := "DSCirculant -> VStack(.., Filt, ...)",
	forTransposition := false,
	isApplicable     := P -> P[1] > 2 and (P[1]-P[2].domain()) > 2 and P[3] <= 0,
	allChildren      := P -> [[ ]],

	rule := function(P, C)
	    local n,l,r,coeffs,nc,ds,ofs,ofs_l,ofs_r,ofs_filt,ds_l,ds_r,ds_filt,j,jj,k,kk,v,
	         filtpoly,filtcoef,filtlen,f;

	    n := P[1];  coeffs := P[2]; nc := coeffs.domain();
	    l := -P[3]; r := -l + nc - 1;

	    ds := P[4]; ofs := P[5];

	    ofs_l := ofs;
	    ofs_filt := (l - ofs_l) mod ds;
	    ofs_r := (n-l-r - ofs_filt) mod ds;

	    ds_l    := CeilingRat((l - ofs_l) / ds);
	    ds_filt := CeilingRat((n-l-r - ofs_filt) / ds);
	    ds_r    := CeilingRat((r - ofs_r) / ds);

	    j := Ind( ds_l );
	    k := Ind( ds_r );
	    f := Ind( ds_filt );
	    v := Ind(nc);
	    jj := Ind(l);
	    kk := Ind(r);

	    filtpoly := Poly(List(coeffs.tolist(),EvalScalar), -l);
	    filtcoef := FillZeros(filtpoly)[1];
	    filtlen := Length(filtcoef);

	    return
	    VStack(
	        BB(ISum(j, j.range,
		     Data(jj, ds*j+ofs_l,
			 Scat(fBase(j)) *
			 RowVec(fCompose(coeffs, Lambda(v, cond(leq(v,r+jj), v-jj+l, v-jj+l-nc)))) *
			 Gath(n, nc, Lambda(v, cond(leq(v,r+jj), v, v+(n-l-r)-1)))))),

		ISum(f, f.range,
		    Scat(fBase(f)) *
		    Blk([ filtcoef ]) *
		    Gath(fAdd(n, filtlen, ds*f + ofs_filt))),

		BB(ISum(k, k.range,
		     Data(kk, ds*k+ofs_r,
			 Scat(fBase(k)) *
			 RowVec(fCompose(coeffs, Lambda(v, cond(leq(v,kk), v-kk+(nc-1), v-kk-1)))) *
			 Gath(n, nc, Lambda(v, cond(leq(v,kk), v, v+(n-l-r)-1))))))
	    );
	end
    )
));


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

Declare(StreamGath);
Declare(StreamScat);
Declare(STensor);
Declare(SIterDirectSum);

Import(compiler);

Class(RootsOfUnity, Sym, rec(
    abbrevs := [ (n) -> [n]],

    def := (n) -> let(j := Ind(n),
	Diag(Lambda(j, omega(n, j)).setRange(Complexes))),

    tolist := self >> self.lambda().tolist(),
    lambda := self >> self.obj.element,
    sums := self >> Diag(self),
    isFunction := true,
    range := self >> TComplex,
    domain := self >> self.params[1],

    transpose := self >> self,

    isReal := self >> (self.params[1] <= 2),

    exportParams := self >> self.params

));

Class(streamDiagFunc, FuncClass, rec(
    def := (N, n, r) -> rec(
        N := N/2,
        n := N,
	r := r,
    ),

    tolist := self >> self.lambda().tolist(),
    lambda := self >> let(
	i := Ind(self.n),
	Lambda(i, imod(i, self.params[2]) * idiv(i, self.params[2]))),
   domain := self >> self.n,
   range := self >> self.N
));

# ==========================================================================
# StreamGath(<i>, <range>, <bs>) - vector gather (read) matrix
# ==========================================================================
Class(StreamGath, BaseMat, SumsBase, rec(
    #-----------------------------------------------------------------------
#    rChildren := self >> [self.func],
#    rSetChild := rSetChildFields("func"),
    rChildren := self >> [self.i],
    rSetChild := rSetChildFields("i"),
    #-----------------------------------------------------------------------
    new := (self, i, range, bs) >> SPL(WithBases(self,
        rec(dimensions := [bs, bs*range], bs := bs, i := i, range := range))),
    #-----------------------------------------------------------------------
    sums := self >> self,
    area := self >> self.bs * self.range,
    isReal := self >> true,
    transpose := self >> StreamScat(self.i, self.range, self.bs),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.i, ", ", self.range,", ", self.bs,")"),
    #-----------------------------------------------------------------------
    toAMat := self >> Gath(fTensor(fBase(self.range, self.i), fId(self.bs))).toAMat()
    # make verilog
));

# ==========================================================================
# StreamScat(<func>, <v>) - vector scatter (write) matrix
# ==========================================================================
Class(StreamScat, BaseMat, SumsBase, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.i],
    rSetChild := rSetChildFields("i"),
    #-----------------------------------------------------------------------
    new := (self, i, range, bs) >> SPL(WithBases(self,
        rec(dimensions := [bs*range, bs], bs := bs, i := i, range := range))),
    #-----------------------------------------------------------------------
    sums := self >> self,
    area := self >> self.func.domain() * self.v,
    isReal := self >> true,
    transpose := self >> StreamGath(self.i, self.range, self.bs),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.i, ", ", self.range,", ", self.bs,")"),
    #-----------------------------------------------------------------------
    toAMat := self >> TransposedAMat(StreamGath(self.i, self.range, self.bs).toAMat())
#    code := (self, y, x) >> VTensor(Scat(self.func), self.v).code(y,x)
));

RCExpandData := function(value)
   return ConcatList(value.v, x->[ReComplex(Complex(x.v)), ImComplex(Complex(x.v))]);
end;

#FixLocalize := (o) -> SubstBottomUpRules(o, [
#    [[Diag, @(1)], e -> Diag(GenerateData(ResolveMemos(e.element))), "FixLocalize" ]]);

Declare(BRAMPerm);

# ===============================================
# CodeBlock(<SPL>)
# ==============================================
Class(CodeBlock, BaseContainer, SumsBase, rec(
    isBlock:=true,

    sums := self >> CopyFields(self, rec(_children := [self.child(1).sums()])),

    code := false,

	createCode := meth(self)
	   local i, s, res, d, dl;
	   if (IsBound(self._children[1].isBlock)) then
	     res := self._children[1].createCode();
	   else
	     s := self.child(1);
	     res := CopyFields(self, rec(code := CodeSums(s, StreamDefaults)));
	   fi;
       res.code := BinSplit(res.code);
	   return res;
	end,

    rChildren := self >> [self._children[1], self.code],

    rSetChild := meth(self, n, what)
        if n=1 then
            self._children[1] := what;
        elif n=2 then
            self.code := what;
        else Error("<n> must be in [1,2]");
        fi;
    end,

    from_rChildren := (self, rch) >> CopyFields(ObjId(self)(rch[1]), rec(code:=rch[2])),

    print := (self,i,is) >> Print(self.name, "(", Cond(self.code<>false, self.code.print(i,is), self._children[1]), ")")
));

Class(StreamSum, ISum, BaseIterative, SumsBase);

# A streaming I tensor J primitive
Class(StreamIxJ, BaseMat, SumsBase, rec(
    new := (self, k, p, bs) >> SPL(WithBases(self, rec(dimensions:=[k*p,k*p], element:=k, p:=p, bs:=bs))),
    print := (self,i,is) >> Print(self.name, "(", self.element, ", ", self.p, ", ", self.bs, ")"),
    toAMat := self >> let(J := When(self.element = 2, I(2), Compose(Tensor(I(2), L(self.element/2, self.element/4)), L(self.element, 2))), Tensor(I(self.p), J).toAMat()),
    transpose := self >> self,
    area := self >> self.element,
    sums := self >> self,
    rChildren := self >> [],
    dimensions := self >> [self.element * self.p, self.element * self.p],
    dims := self >> self.dimensions,
   isReal := True
));

# parameters:
#   L[1]: SPL
#   L[2]: p: p in I_p x SPL
#   L[3]: bs: stream buffer size
Class(STensor, Tensor,
  rec(
    new := (self, L) >> SPL(WithBases(self, rec(
            bs := L[3],
            dimensions := L[1].dimensions * Cond(IsValue(L[2]), L[2].ev(), L[2]),
            p := Cond(L[1].dims()[1] >= L[3], L[2], L[2]*L[1].dims()[1]/L[3]),
            _children := [Checked(L[1].dims()[1] = L[1].dims()[2],
                          Cond(L[1].dims()[1] >= L[3], L[1], Tensor(I(L[3] / L[1].dims()[1]), L[1]))), Cond(L[1].dims()[1] >= L[3], L[2], L[2]*L[1].dims()[1]/L[3]), L[3]]
#            _children := [Checked(L[1].dims()[1] = L[1].dims()[2],
#                          Cond(L[1].dims()[1] >= L[3], L[1], Tensor(I(L[3] / L[1].dims()[1]), L[1])))]
                          ))),

    print := (self,i,is) >> Print(self.name, "(", self.child(1), ", ", self.p, ", ", self.bs, ")"),
    createCode := self >> Cond(IsBound(self.child(1).createCode), STensor(self.child(1).createCode(), self.p, self.bs), self),
    isPermutation := False,
    toAMat := self >> Tensor(I(self.p), self.child(1)).toAMat(),
    sums := self >> self,
    dims := self >> Cond(IsValue(self.p), self.p.ev(), self.p) * self.child(1).dims()
));

# parameters: I_m x L^n_r x I_p
#   L[1]: n
#   L[2]: r
#   L[3]: p
#   L[4]: m
#   L[5]: stream buffer size
Class(SIxLxI, Tensor,
  rec(
    new := (self, l) >> SPL(WithBases(self, rec(
            _children := [],
            dimensions := [l[1] * l[3] * l[4], l[1] * l[3] * l[4]],
            bs := l[5],
            p := l[3],
            m := l[4],

    createCode := self >> self,
    print := (self,i,is) >> Print(self.name, "(", l[1], ", ", l[2], ", ", self.p, ", ", self.m, ", ", self.bs, ")"),
    isPermutation := False,
    toAMat := self >> Tensor(Tensor(I(self.m), L(l[1], l[2]), I(self.p))).toAMat(),
    transpose := self >> self,
    dims := self >> self.dimensions,
#    dimensions := l[1] * l[3] * l[4],
    sums := self >> self,
)))));

# ==========================================================================
# Streaming iterative direct sum
# SIterDirectSum(<var>, <range>, <spl>, <stream buffer size>)
#    Size of the <spl> must not depend on <var>.
# ==========================================================================
Class(SIterDirectSum, BaseIterative, rec(
    abbrevs := [ (v,d,e, bs) -> [v,d,e,bs],
	         (v,e,bs) -> [v, v.range, e, bs]],
    new := meth(self, var, domain, expr, bs)
        local res;
        var.isLoopIndex := true;
        res := SPL(WithBases(self, rec(
#		_children := [Checked(expr.dims()[1] <= bs and
		_children := [Checked(expr.dims()[1] = expr.dims()[2],
				      Cond(expr.dims()[1] >= bs,
					  expr,
					  IterDirectSum(Ind(bs/expr.dims()[1]), expr)))],
		var := var,
		bs := bs,
		dimensions := expr.dimensions * domain,
		domain := domain)));
        return res;
        end,

    rChildren := self >> self._children,
    rSetChild := meth(self, n, what)  self._children[n] := what; end,
    child := (self, n) >> self._children[n],
    children := self >> self._children,

#         Constraint(IsSPL(expr));
# 	Constraint(not IsList(domain));
# 	#domain := toRange(domain);
# 	if domain = 1 then
# 	    return SubstBottomUp(expr, @.cond(e->Same(e,var)), e->V(0));
# 	fi;
# 	var.range := domain;
#         return SPL(WithBases( self,
# 	    rec( expr := expr,
# 		 var := var,
# 		 domain := domain,
# 		 dimensions := domain * Dimensions(expr) )));
#     end,

    print := (self, i, is) >> Print(self.name, "(", self.var, ", ",  self.domain, ", ", self.child(1).print(i+is, is), ", ", self.bs, ")"),

    toAMat := self >> self.unroll().toAMat(),

    unroll := self >> DirectSum(
	List([ 0 .. self.domain - 1 ],
	    index_value -> SubstBottomUp(
		Copy(self._children[1]), self.var,
		e -> V(index_value)))),

    dims := self >> self.dimensions,
    #-----------------------------------------------------------------------
    isPermutation := False,
    #-----------------------------------------------------------------------
    createCode := self >> Cond(IsBound(self._children[1].createCode),
	SIterDirectSum(self.var, self.domain, self._children[1].createCode(), self.bs), self),

    transpose :=   # we use inherit to copy all fields of self
        self >> Inherit(self, rec(
		   expr := TransposedSPL(self._children[1]),
		   dimensions := Reversed(self.dimensions))),

    sums := self >> self
));

NoPull.createCode := meth ( self )
    local i, s, res, d, dl;
    if IsBound(self._children[1].isBlock)  then
        res := self._children[1].createCode();
    else
        s := self.child(1);
        res := SPL(CopyFields(self, rec(
                code := CodeSums(self.dims()[1] * 2, s) )));
    fi;
    for i  in [ 1 .. 10 ]  do
        BinSplit(res.code);
    od;
    return res;
end;

Class(Omega, BaseIterative, rec(

    abbrevs := [ (N,n,i) ->[N, n, i] ],

    new := (self, N, n, i) >> let(j := Ind(n),
        Lambda(j, omega(N, i*j)).setRange(TComplex)),

    rChildren := self >> self._children,
    rSetChild := meth(self, n, what)  self._children[n] := what; end,
    child := (self, n) >> self._children[1],
    children := self >> self._children,

    tolist := self >> self.lambda().tolist(),
    lambda := self >> self.obj.element,
    sums := self >> Diag(self),
    isFunction := true,
    range := self >> TComplex,
    domain := self >> self.params[1],

    transpose := self >> self,

#    isReal := self >> (self.params[1] = self.params[2]  or self.params[2] = 1),

    exportParams := self >> self.params
));

Prm.createCode := (self) >> SPL(CopyFields(self, rec(code := CodeSums(self, StreamDefaults))));
ISum.createCode := (self) >> CodeBlock(self).createCode();

Tensor.createCode := self >>
   Tensor(
      List([1..Length(self.children())], i->
        Cond(IsBound(self.child(i).createCode),
	    self.child(i).createCode(),
	    self.child(i)
         )
      )
   );

Tw1Stride := (n,d,k,which,stride) -> Checked(IsPosIntSym(n), IsPosIntSym(d), IsPosIntSym(k),
    fCompose(dOmega(n,k), diagTensor(dLin(n/d, 1, 0, TInt), dLin(d/stride, 1, which, TInt))));

# fReal := (f) -> fCompose(RCData(f), fTensor(fId(f.n), fBase(2, 0)));
Class(fReal, FuncClass, rec(
   def := (f) -> rec(N := f.range(), n := f.domain(), f := f),
   tolist := self >> self.lambda().tolist(),
   lambda := self >> fCompose(RCData(self.f), fTensor(fId(self.n), fBase(2,0))).lambda(),
   domain := self >> self.n,
   range := self >> self.N
));

# fImag := (f) -> fCompose(RCData(f), fTensor(fId(f.n), fBase(2, 1)));
Class(fImag, FuncClass, rec(
   def := (f) -> rec(N := f.range(), n := f.domain(), f := f),
   tolist := self >> self.lambda().tolist(),
   lambda := self >> fCompose(RCData(self.f), fTensor(fId(self.n), fBase(2,1))).lambda(),
   domain := self >> self.n,
   range := self >> self.N
));

Class(Stream1DFT2, BaseMat, SumsBase, rec(
    new := (self) >> SPL(WithBases(self, rec(dimensions:=[2,2]))),
    print := (self,i,is) >> Print(self.name),
    toAMat := self >> DFT(2,1).toAMat(),
    transpose := self >> self,
    sums := self >> self,
    rChildren := self >> [],
    dimensions := [2,2],
    dims := self >> self.dimensions,
));

Class(StreamPadDiscard, BaseMat, SumsBase, rec(
    abbrevs := [(n, m, w_in, w_out) -> [n, m, w_in, w_out]],

    new := (self, n, m, w_in, w_out) >> SPL(WithBases(self, rec(
          _children := [n,m, w_in, w_out],
          dimensions := [n, m],
    ))),

    rChildren := self >> self._children,
	rSetChild := meth(self, n, what) self._children[n] := what; end,
	child     := (self, n) >> self._children[n],
	children  := self >> self._children,

    toAMat := self >> RI(self.child(1), self.child(2)).toAMat(),
    dims := self >> [self.child(1), self.child(2)],

));


# run this file with
#   gap -b -q < /dev/null group.gap | perl -pe 's/\\\n//s' | indent -kr

Print("/* ----- data generated by group.gap begins ----- */\n\n");
Print("struct group {\n    unsigned long autosize;\n");
Print("    int order, ngens;\n    const char *gens;\n};\n");
Print("struct groups {\n    int ngroups;\n");
Print("    const struct group *groups;\n};\n\n");
Print("static const struct group groupdata[] = {\n");
offsets := [0];
offset := 0;
for n in [2..26] do
  Print("    /* order ", n, " */\n");
  for G in AllSmallGroups(n) do

    # Construct a representation of the group G as a subgroup
    # of a permutation group, and find its generators in that
    # group.

    # GAP has the 'IsomorphismPermGroup' function, but I don't want
    # to use it because it doesn't guarantee that the permutation
    # representation of the group forms a Cayley table. For example,
    # C_4 could be represented as a subgroup of S_4 in many ways,
    # and not all of them work: the group generated by (12) and (34)
    # is clearly isomorphic to C_4 but its four elements do not form
    # a Cayley table. The group generated by (12)(34) and (13)(24)
    # is OK, though.
    #
    # Hence I construct the permutation representation _as_ the
    # Cayley table, and then pick generators of that. This
    # guarantees that when we rebuild the full group by BFS in
    # group.c, we will end up with the right thing.

    ge := Elements(G);
    gi := [];
    for g in ge do
      gr := [];
      for h in ge do
        k := g*h;
	for i in [1..n] do
	  if k = ge[i] then
	    Add(gr, i);
	  fi;
	od;
      od;
      Add(gi, PermList(gr));
    od;

    # GAP has the 'GeneratorsOfGroup' function, but we don't want to
    # use it because it's bad at picking generators - it thinks the
    # generators of C_4 are [ (1,2)(3,4), (1,3,2,4) ] and that those
    # of C_6 are [ (1,2,3)(4,5,6), (1,4)(2,5)(3,6) ] !

    gl := ShallowCopy(Elements(gi));
    Sort(gl, function(v,w) return Order(v) > Order(w); end);

    gens := [];
    for x in gl do
      if gens = [] or not (x in gp) then
        Add(gens, x);
	gp := GroupWithGenerators(gens);
      fi;
    od;

    # Construct the C representation of the group generators.
    s := [];
    for x in gens do
      if Size(s) > 0 then
        Add(s, '"');
        Add(s, ' ');
        Add(s, '"');
      fi;
      sep := "\\0";
      for i in ListPerm(x) do
        chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Add(s, chars[i]);
      od;
    od;
    s := JoinStringsWithSeparator(["    {", String(Size(AutomorphismGroup(G))),
                                   "L, ", String(Size(G)),
                                   ", ", String(Size(gens)),
                                   ", \"", s, "\"},\n"],"");
    Print(s);
    offset := offset + 1;
  od;
  Add(offsets, offset);
od;
Print("};\n\nstatic const struct groups groups[] = {\n");
Print("    {0, NULL}, /* trivial case: 0 */\n");
Print("    {0, NULL}, /* trivial case: 1 */\n");
n := 2;
for i in [1..Size(offsets)-1] do
  Print("    {", offsets[i+1] - offsets[i], ", groupdata+",
        offsets[i], "}, /* ", i+1, " */\n");
od;
Print("};\n\n/* ----- data generated by group.gap ends ----- */\n");
quit;