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

Class(BaseVGath, BaseMat, SumsBase, rec(
    isVGath := true,
    sums := self >> self,
    isReal := self >> true,
    dims := self >> Error(ObjId(self), ".dims() is undefined"),
    needInterleavedLeft := False,
    needInterleavedRight := False,
    conjTranspose := self >> self.transpose(),
));

Class(BaseSVGath, BaseMat, SumsBase, rec(
    isVGath := true,
    isSVGath := true,
    #-----------------------------------------------------------------------
    abbrevs := [
	(func, v, sv) -> Checked(
	    IsFunction(func) or IsFuncExp(func), IsInt(v), IsInt(sv),
	    [func, v, sv, Cond(CanBeFullyEvaluated(func.domain()), EvalScalar((func.domain()*sv mod v) / sv), Unk(TInt))]),
	(func, v, sv, rem) -> Checked(
	    IsFunction(func) or IsFuncExp(func), IsInt(v), IsInt(sv), rem _is Unk or IsInt(_unwrap(rem)),
	    [func, v, sv, rem]),
    ],
    #-----------------------------------------------------------------------
    new := (self, func, v, sv, rem) >> SPL(WithBases(self,
        rec(func := func, v := v, sv := sv, rem := rem))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> Error(ObjId(self), ".dims() is undefined"),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.__name__, "(", self.func, ", ",
        self.v, ", ", self.sv, ", ", self.rem, ")", self.printA()),
    #-----------------------------------------------------------------------
    getConstantRem := self >> Cond(
	self.rem _is Unk and not IsSymbolic(EvalScalar(self.func.domain())),
	    EvalScalar(imod(self.func.domain()*self.sv, self.v) / self.sv),
	self.rem _is Unk,
	    Error("Can't generate sub-vector gather/scatter code for <self>, because its dimensions are unknown"),
	self.rem
    ),
    #-----------------------------------------------------------------------
    conjTranspose := self >> self.transpose(),

    sums := self >> self,
    isReal := self >> true,
    needInterleavedLeft := False,
    needInterleavedRight := True
));

IsVGath := x -> IsRec(x) and IsBound(x.isVGath) and x.isVGath;
IsSVGath := x -> IsRec(x) and IsBound(x.isSVGath) and x.isSVGath;

#F ==========================================================================
#F VGath(<func>, <v>) - vector gather (read) matrix, assumes aligned input
#F
#F <func> must be a vector index function returning vector indices
#F
#F VGath(f, v) == Gath(fTensor(f, fId(v)))
#F ==========================================================================
Class(VGath, BaseVGath, SumsBase, rec(
    #-----------------------------------------------------------------------
    #F NOTE: rChildren: does not expose .v!
    rChildren := self >> [self.func],
    rSetChild := rSetChildFields("func"),
    from_rChildren := (self, rch) >> ObjId(self)(rch[1], self.v),
    #-----------------------------------------------------------------------
    new := (self, func, v) >> SPL(WithBases(self,
        rec(func := func, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [self.v*self.func.domain(), self.v*self.func.range()],
    #-----------------------------------------------------------------------
    area := self >> self.func.domain() * self.v,
    transpose := self >> VScat(self.func, self.v),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.func, ", ", self.v,")", self.printA()),
    #-----------------------------------------------------------------------
    toAMat := self >> let(v:=self.v, n := EvalScalar(v*self.func.domain()),
        N := EvalScalar(v*self.func.range()),
        func := fTensor(self.func, fId(v)).lambda(),
        AMatMat(List([0..n-1], row -> BasisVec(N, EvalScalar(func.at(row).ev()))))),
));

#F ==========================================================================
#F VGath_u(<func>, <v>) -- assumes input is unaligned, and uses scalar <func>
#F
#F VGath_u performs an unaligned gather operations. Thus <func> must be an
#F index function returning scalar, not vector, addresses (unlike VGath).
#F
#F In particular:
#F   VGath(func, vlen) = VGath_u(fTensor(func, fBase(vlen, 0)), vlen)
#F ==========================================================================
Class(VGath_u, VGath, rec(
    new := (self, func, v) >> SPL(WithBases(self,
        rec(func := func, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [self.v*self.func.domain(), self.func.range()],
    #-----------------------------------------------------------------------
    toAMat := self >> let(v:=self.v,
        n := EvalScalar(v*self.func.domain()),
        N := EvalScalar(self.func.range()),
        func := self.func.lambda(),
        AMatMat(List([0..n-1], row -> BasisVec(N, EvalScalar(add(func.at(idiv(row,v)), imod(row, v)).ev()))))),
    #-----------------------------------------------------------------------
    transpose := self >> VScat_u(self.func, self.v)
));

#F ==========================================================================
#F VGath_zero(N, n, <v>) - vector gather matrix
#F ==========================================================================
Class(VGath_zero, BaseVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.N, self.n, self.v],
    rSetChild := rSetChildFields("N", "n", "v"),
    #-----------------------------------------------------------------------
    new := (self, N, n, v) >> SPL(WithBases(self,
        rec(n := n, N := N, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [self.v * self.n, self.v * self.N],
    #-----------------------------------------------------------------------
    transpose := self >> VScat_zero(self.N, self.n, self.v),
    toAMat := self >> VGath(fAdd(self.N, self.n, 0), self.v).toAMat(),
 ));

#F ==========================================================================
#F VGath_dup(<func>, <v>) - gathers scalar values and replicates <v> times to
#F                          obtain a vector values (read) matrix
#F Example:
#F spiral> PrintMat(MatSPL(VGath_dup(fId(4), 2)));
#F [ [ 1,  ,  ,   ],
#F   [ 1,  ,  ,   ],
#F   [  , 1,  ,   ],
#F   [  , 1,  ,   ],
#F   [  ,  , 1,   ],
#F   [  ,  , 1,   ],
#F   [  ,  ,  , 1 ],
#F   [  ,  ,  , 1 ] ]
#F
#F This is performed using shuffles in SSE/SSE2, or movddup in SSE4.
#F
#F The transpose of VGath_dup is VScat_red (red = reduce) incurs
#F arithmetic ops.
#F
#F spiral> PrintMat(MatSPL(VScat_red(fId(4), 2)));
#F [ [ 1, 1,  ,  ,  ,  ,  ,   ],
#F   [  ,  , 1, 1,  ,  ,  ,   ],
#F   [  ,  ,  ,  , 1, 1,  ,   ],
#F   [  ,  ,  ,  ,  ,  , 1, 1 ] ]
#F
#F Some architectures have special instructions for this operation.
#F For example, SSE4 has 'hadd' == horizontal add.
#F
#F ==========================================================================
Declare(VGath_dup);
Class(VGath_dup, VGath, rec(
    #-----------------------------------------------------------------------
    new := (self, func, v) >> SPL(WithBases(self,
        rec(func := func, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [self.v*self.func.domain(), self.func.range()],
    #-----------------------------------------------------------------------
    transpose := self >> Error("not yet supported"), #VScat_red(self.func, self.v),
    #-----------------------------------------------------------------------
    toAMat := self >> Tensor(
        Gath(self.func),
        Mat(TransposedMat([Replicate(self.v, 1)]))).toAMat(),
    #-----------------------------------------------------------------------
    toloop := (self, bksize) >> let(i := Ind(self.func.domain() / bksize),
	ISum(i, i.range,
            VScat(fTensor(fBase(i), fId(bksize)),self.v) *
            VGath_dup(fCompose(self.func, fTensor(fBase(i), fId(bksize))),self.v)))
    #-----------------------------------------------------------------------
));

#F ==========================================================================
#F VGath_dup_x_I(<func>, <v>) - gathers vector values and replicates <v> times
#F NOTE: EXPLAIN THIS
#F ==========================================================================
VGath_dup_x_I := (func, v) -> Cond(
    func.domain() = 1,
        VReplicate(v),
    # else
    let(i := Ind(func.domain()),
	ISum(i, i.range,
	    VScat(fTensor(fBase(i),fId(v)), v) *
	    VReplicate(v) *
	    VGath(fBase(i),v)) *
        VGath(func,v)
    )
);

#F ==========================================================================
#F VGath_sv(<func>, <v>, <sv>) - vector gather (read) matrix on subvectors
#F NOTE: EXPLAIN THIS
#F ==========================================================================
Class(VGath_sv, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func],    # self >> [self.func, self.v, self.sv],
    rSetChild := rSetChildFields("func"), # rSetChildFields("func", "v", "sv"),
    from_rChildren := (self, rch) >> ObjId(self)(rch[1], self.v, self.sv, self.rem),
    #-----------------------------------------------------------------------
    dims := self >> [_roundup(self.sv * self.func.domain(), self.v),
                     self.sv * self.func.range() ],
    #-----------------------------------------------------------------------
    area := self >> 2 * self.func.domain() * self.sv,
    #-----------------------------------------------------------------------
    transpose := self >> VScat_sv(self.func, self.v, self.sv, self.rem),
    #-----------------------------------------------------------------------
    toAMat := self >> let(sv:=self.sv,
        n := EvalScalar(sv * self.func.domain()), N := EvalScalar(Cols(self)),
        nv := EvalScalar(Rows(self)),
	func := fTensor(self.func, fId(sv)).lambda(),
        Scat(fAdd(nv, n, 0)).toAMat() *
        AMatMat(List([0..n-1], row -> BasisVec(N, EvalScalar(func.at(row)))))),
));

#F ==========================================================================
#F RCVGath_sv(<func>, <v>, <sv>) - vector gather (read) matrix on subvectors
#F NOTE: EXPLAIN THIS
#F ==========================================================================
Class(RCVGath_sv, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func],    # self >> [self.func, self.v, self.sv],
    rSetChild := rSetChildFields("func"), # rSetChildFields("func", "v", "sv"),
    from_rChildren := (self, rch) >> ObjId(self)(rch[1], self.v, self.sv, self.rem),
    #-----------------------------------------------------------------------
    dims := self >> let(
        n := self.func.domain() * self.sv, nv := _roundup(n, self.v),
	N := self.func.range() * self.sv,
        2 * [nv, N]),
    #-----------------------------------------------------------------------
    area := self >> 2 * self.func.domain() * self.sv,
    #-----------------------------------------------------------------------
    transpose := self >> RCVScat_sv(self.func, self.v, self.sv, self.rem),
    #-----------------------------------------------------------------------
    toAMat := self >> let(
	sv := EvalScalar(self.sv), n := EvalScalar(self.func.domain()),
	N  := EvalScalar(self.func.range()),
        func := fTensor(self.func, fId(sv), fId(2)).lambda(),
        Scat(fTensor(fAdd(_roundup(n, self.v), n, 0), fId(2))).toAMat() *
	AMatMat(List([0..2*n-1], row -> BasisVec(2*N, EvalScalar(func.at(row).ev()))))),
));

#F ==========================================================================
#F VStretchGath(<func>, <part>, <v>) -
#F   vector gather (read) matrix on subvectors
#F   reads according to func but fills up entire vectors
#F NOTE: EXPLAIN THIS
#F ==========================================================================
Class(VStretchGath, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func],
    rSetChild := rSetChildFields("func"),
    from_rChildren := (self, rch) >> ObjId(self)(rch[1], self.part, self.v),
    #-----------------------------------------------------------------------
    dims := self >> let(
	n  := self.func.domain(), N := self.func.range(),
	nv := self.part * _roundup(n/self.part, self.v),
        [nv, N]),
    #-----------------------------------------------------------------------
    abbrevs := [],
    new := (self, func, part, v) >> SPL(WithBases(self,
        rec(func := func, part := part, v := v))).setDims(),
    #-----------------------------------------------------------------------
    area := self >> self.func.domain(),
    #-----------------------------------------------------------------------
    transpose := self >> VStretchScat(self.func, self.part, self.v),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.func, ", ",
        self.part, ", ", self.v, ")", self.printA()),
    #-----------------------------------------------------------------------
    toAMat := self >> let(
	n  := EvalScalar(self.func.domain()),
	N  := EvalScalar(self.func.range()),
	nv := self.part * _roundup(n/self.part, self.v),
        Tensor(I(self.part), Scat(fAdd(nv/self.part, n/self.part, 0))).toAMat() *
	AMatMat(List([0..n-1], row ->
		    BasisVec(N, EvalScalar(self.func.at(row).ev()))))),
));

#F ==========================================================================
#F vRCStretchGath(<func>, <parts>, <v>) -
#F   vector gather (read) matrix on subvectors
#F   reads according to func but fills up entire vectors
#F NOTE: EXPLAIN THIS
#F ==========================================================================
Class(vRCStretchGath, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func],
    rSetChild := rSetChildFields("func"),
    from_rChildren := (self, rch) >> ObjId(self)(rch[1], self.part, self.v),
    #-----------------------------------------------------------------------
    dims := self >> let(
	n  := self.func.domain(), N := self.func.range(),
	nv := self.part * _roundup(n/self.part, self.v/2),
	2*[nv, N]),
    #-----------------------------------------------------------------------
    abbrevs := [],
    new := (self, func, part, v) >> SPL(WithBases(self,
        rec(func := func, part := part, v := v))).setDims(),
    #-----------------------------------------------------------------------
    area := self >> 2 * self.func.domain(),
    #-----------------------------------------------------------------------
    transpose := self >> vRCStretchScat(self.func, self.part, self.v),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.func, ", ",
        self.part, ", ", self.v, ")", self.printA()),
    #-----------------------------------------------------------------------
    toAMat := self >> let(
	n  := self.func.domain(), N := self.func.range(),
	nv := self.part * _roundup(n/self.part, self.v/2),
        func := fTensor(self.func, fId(2)),
        Tensor(I(self.part),
	       Scat(fAdd(nv/self.part, n/self.part, 0)), I(2)).toAMat() *
	AMatMat(List([0..2*n-1], row -> BasisVec(2*N, EvalScalar(func.at(row).ev()))))),
));

#F ==========================================================================
#F RCVStretchGath(<func>, <parts>, <v>) -
#F   vector gather (read) matrix on subvectors
#F   reads according to func but fills up entire vectors
#F NOTE: EXPLAIN THIS
#F ==========================================================================
Class(RCVStretchGath, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func],
    rSetChild := rSetChildFields("func"),
    from_rChildren := (self, rch) >> ObjId(self)(rch[1], self.part, self.v),
    #-----------------------------------------------------------------------
    dims := self >> let(
	n  := self.func.domain(), N := self.func.range(),
	nv := self.part * _roundup(n/self.part, self.v),
	2*[nv, N]),
    #-----------------------------------------------------------------------
    abbrevs := [],
    new := (self, func, part, v) >> SPL(WithBases(self,
        rec(func := func, part := part, v := v))).setDims(),
    #-----------------------------------------------------------------------
    area := self >> 2 * self.func.domain() * self.v,
    #-----------------------------------------------------------------------
    transpose := self >> RCVStretchScat(self.func, self.part, self.v),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.func, ", ",
        self.part, ", ", self.v, ")", self.printA()),
    #-----------------------------------------------------------------------
    toAMat := self >> Tensor(VStretchGath(self.func, self.part, self.v),
                             I(2)).toAMat(),
));

#F ==========================================================================
#F VGath_pc(N, n, ofs, v) - gather for unaligned partial contiguous vectors
#F   reads <n> contiguous values out of <N> at an offset <ofs>
#F   outputs ceil(<n> / <v>) vectors
#F
#F   .N   -- input size, only affects dimensions of VGath_pc matrix
#F   .n   -- how many contiguous points we are reading
#F   .ofs -- starting offset
#F   .v   -- vector length
#F
#F Example:
#F
#F spiral> PrintMat(MatSPL(VGath_pc(5, 3, 0, 4)));
#F [ [ 1,  ,  ,  ,   ],
#F   [  , 1,  ,  ,   ],
#F   [  ,  , 1,  ,   ],
#F   [  ,  ,  ,  ,   ] ]
#F
#F ==========================================================================
Class(VGath_pc, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.N, self.n, self.ofs, self.v],
    rSetChild := rSetChildFields("N", "n", "ofs", "v"),
    #-----------------------------------------------------------------------
    abbrevs := [],
    new := (self, N, n, ofs, v) >> SPL(WithBases(self, rec(
        N := N, n := n, ofs := ofs, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [ _roundup(self.n, self.v), self.N ],
    #-----------------------------------------------------------------------
    area := self >> self.n,
    #-----------------------------------------------------------------------
    transpose := self >> VScat_pc(self.N, self.n, self.ofs, self.v),
    #-----------------------------------------------------------------------
    toAMat := self >> let(n := self.n, N := self.N, nv := _roundup(n, self.v),
        Scat(fAdd(nv, n, 0)).toAMat() * Gath(fAdd(N, n, self.ofs)).toAMat()),
    #-----------------------------------------------------------------------
    print :=  (self, i, is) >> Print(self.__name__, "(", self.N,
        ", ", self.n, ", ", self.ofs, ", ", self.v, ")", self.printA()),
));

#F ==========================================================================
#F IxVGath_pc(k, N, n, ofs, v)
#F
#F Performs multiple stacked VGath_pc operations
#F
#F IxVGath_pc(k, N, n, ofs, v) = I(k) tensor VGath_pc(N, n, ofs, v)
#F
#F See also: VGath_pc
#F
#F Example:
#F
#F spiral> PrintMat(MatSPL(VGath_pc(5, 3, 0, 4)));
#F [ [ 1,  ,  ,  ,   ],
#F   [  , 1,  ,  ,   ],
#F   [  ,  , 1,  ,   ],
#F   [  ,  ,  ,  ,   ] ]
#F
#F spiral> PrintMat(MatSPL(IxVGath_pc(2, 5, 3, 0, 4)));
#F [ [ 1,  ,  ,  ,  ,  ,  ,  ,  ,   ],
#F   [  , 1,  ,  ,  ,  ,  ,  ,  ,   ],
#F   [  ,  , 1,  ,  ,  ,  ,  ,  ,   ],
#F   [  ,  ,  ,  ,  ,  ,  ,  ,  ,   ],
#F   [  ,  ,  ,  ,  , 1,  ,  ,  ,   ],
#F   [  ,  ,  ,  ,  ,  , 1,  ,  ,   ],
#F   [  ,  ,  ,  ,  ,  ,  , 1,  ,   ],
#F   [  ,  ,  ,  ,  ,  ,  ,  ,  ,   ] ]
#F
#F ==========================================================================
Class(IxVGath_pc, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.k, self.N, self.n, self.ofs, self.v],
    rSetChild := rSetChildFields("k", "N", "n", "ofs", "v"),
    #-----------------------------------------------------------------------
    abbrevs := [],
    new := (self, k, N, n, ofs, v) >> SPL(WithBases(self, rec(
        k := k, N := N, n:= n, ofs := ofs, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [ self.k*_roundup(self.n, self.v), self.k*self.N ],
    #-----------------------------------------------------------------------
    area := self >> self.k * self.n,
    #-----------------------------------------------------------------------
    transpose := self >> IxVScat_pc(self.k, self.N, self.n, self.ofs, self.v),
    #-----------------------------------------------------------------------
    toloop := (self, bksize) >> let(
	j := Ind(self.k),
	gath := VGath_pc(self.k*self.N, self.n, self.ofs + j*self.N, self.v),
	nvecs := Rows(gath) / self.v,
	ISum(j, j.range,
             VScat(HH(self.k*nvecs, nvecs, j*nvecs, [1]), self.v) *
	     gath).split(bksize)),
    #-----------------------------------------------------------------------
    toAMat := self >>
        Tensor(I(self.k), VGath_pc(self.N, self.n, self.ofs, self.v)).toAMat(),
    #-----------------------------------------------------------------------
    print :=  (self, i, is) >> Print(self.__name__, "(", self.k, ", ", self.N,
        ", ", self.n, ", ", self.ofs, ", ", self.v, ")", self.printA()),
));

#F ==========================================================================
#F IxRCVGath_pc(k, N, n, ofs, v)
#F   vector gather (read) matrix reading unaligned
#F   partial contiguous COMPLEX vectors
#F
#F   IxRCVGath_pc(..params..) == IxVGath_pc(..params..) tensor I(2)
#F
#F  Note: IxRCVGath_pc(k, N, n, ofs, v) == IxVGath_pc(k, 2*N, 2*n, 2*ofs, 2*v)
#F        observe 2*v inside IxVGath_pc.
#F
#F        IxVGath_pc(j, 2*N, 2*n, 2*ofs, v), however, is not exactly equal
#F        it has a different # of rows, if n < v/2
#F ==========================================================================
Class(IxRCVGath_pc, BaseSVGath, rec(
    #-----------------------------------------------------------------------
    rChildren := self >> [self.k, self.N, self.n, self.ofs, self.v],
    rSetChild := rSetChildFields("k", "N", "n", "ofs", "v"),
    #-----------------------------------------------------------------------
    abbrevs := [],
    new := (self, k, N, n, ofs, v) >> SPL(WithBases(self, rec(
        k := k, N := N, n := n, ofs := ofs, v := v))).setDims(),
    #-----------------------------------------------------------------------
    dims := self >> [ 2*self.k*_roundup(self.n, self.v),  2*self.k*self.N ],
    #-----------------------------------------------------------------------
    area := self >> 2 * self.k * self.n,
    #-----------------------------------------------------------------------
    transpose := self >> IxRCVScat_pc(self.k, self.N, self.n, self.ofs, self.v),
    #-----------------------------------------------------------------------
    toAMat := self >>
        Tensor(IxVGath_pc(self.k, self.N, self.n, self.ofs, self.v), I(2)).toAMat(),
    #-----------------------------------------------------------------------
    print :=  (self, i, is) >> Print(self.__name__, "(", self.k, ", ", self.N,
        ", ", self.n, ", ", self.ofs, ", ", self.v, ")", self.printA()),
));


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

myScript := function()
    local k, filename;

    for k in [16, 32, 64, 128] do
        filename := ConcatenationString("fft", String(k));
        HDLSynthesize(streamDFTUnroll(k, 2, 4), 1, 0, 16, 300, 0, filename);
    od;
end;


Class(GathIntoVec, BaseOperation, rec(
  __call__ := (self, sr, var, range) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
	var := var,
	range := range,
    _children := [range]
  ))),
  dims := self >> [self.var, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.var, ", ", self.range, ")"
  )
));

DefaultSumsGen.GathIntoVec := (self, o, opts) >> o;

DefaultCodegen.GathIntoVec := meth(self, o, y, x, opts)
  local i, x_first, x_last;
  if (IsBound(opts.isCSR) and opts.isCSR) then
    i := var.fresh_t("i", x.t);
    x_first := var.fresh_t("x_first", x.t);
    x_last := var.fresh_t("x_last", x.t);
	return	decl([i, x_first, x_last], chain (
		assign(x_first, add(x, o.var)),
		assign(x_last, add(x, add(o.var, V(1)))),

		loopf(i, deref(x_first), deref(x_last)),
		  chain(
			assign(deref(add(y,sub(i,x_first))), sub(deref(x_last), deref(x_first)))
		  )
		)
	);
  fi;
end;


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

# Cross-compatibility
#

# MAJOR FORWARD DECLARATION HACK
#
# the current module is loaded prior to paradigms.cache, but we rely on the ACache tag
# declared there. To get around this we create a function, which is evaluated on
# execution, to map ACache to a shorter name. This is necessary so that ObjId comparisons
# work at the tag level.
#
# NOTE: this cannot be done with
#
# _ACache := spiral.paradigms.cache.ACach
#
# because assignments are evaluated immediately, and paradigms.cache does not yet exist.
#
# NOTE: we do the same with the CacheSpec and AInplace

_ACache := () -> spiral.paradigms.cache.ACache;

_CacheDesc := () -> spiral.paradigms.cache.CacheDesc;

_AInplace := () -> spiral.paradigms.cache.AInplace;

_AExpRight := () -> spiral.paradigms.cache.AExpRight;

_AIO := () -> spiral.paradigms.common.AIO;

_ABuf := () -> spiral.paradigms.loops.ABuf;
#
## _divisorCacheTags
#
# takes the ACache(N, ...) with the largest N from 't' and
# tests whether the divisor pair 'd' would work well with the cache
#
# We assume a (W x I) (I x W) expansion, so the left side is
# strided. We also assume out-of-place expansion.
#
# At some point, support for an 'Inplace' tag will be added.
#
# the criteria for working well with cache are as follows:
#
# let m = d[1], n = d[2]
#
# LEFT side: kernel size 'm', stride 'n'
# RIGHT side: kernel size 'n', stride 1
#
# cache specs are [B,S,A,R]
# T is the datatype, eg. float, double
#
# let E = B / sizeof(T)
#
# criteria:
#
# --> see the comments in the code
#
# NOTE: additions to support Inplace tag added, but changes incomplete.

_divisorCacheTags := function(d, t)
    local taglist, N, tag, cs, m, n, elems, csize, cond, inplace;

    taglist := t.getTag(_ACache());

    # tagged for inplace?
    inplace := t.getTag(_AInplace());

    # if we don't have any ACache tags, accept this divisor pair.
    if taglist = false then
        return true;
    fi;

    # we need taglist to be a list
    taglist := When(IsList(taglist), taglist, [taglist]);

    # find the max cache level amongst the tags.
    N := Maximum(List(taglist, e -> e.params[1]));

    # use the first tag which matches it.
    tag := Filtered(taglist, e -> e.params[1] = N)[1];

    # extract the cache spec from the tag
    cs := tag.params[2];

    # paranoia
    Constraint(ObjId(cs) = _CacheDesc());

    # d[1] LEFT side expansion
    # d[2] RIGHT side expansion
    m := d[1]; n := d[2];

    # cache sizes
    elems := cs.blksize;
    csize := elems * cs.nsets * cs.assoc;

    # associativity must be at least 2 for outofplace, otherwise read and
    # write overwrite each other
    if inplace = false and cs.assoc < 2 then
        return false;
    fi;

    # if any of these conditions are true -- we allow this split
    cond := [];

    # if the whole thing fits in cache, let it through
    Add(cond, n * m < csize);

    # if right side is larger than cache, left side only uses the assoc so
    # size must be smaller than it.. (also accounting for read/write)
    Add(cond, (n > elems*cs.nsets) and (m <= (cs.assoc / 2)));

    # if right side fits in cache, left side strides partially into the cache,
    # and the size constraint on 'm' is a function of 'n'
    Add(cond, (n <= csize / 2) and (m <= (csize / (2 * n))));

    #PrintLine(d, ": ", cond);

    return ForAny(cond, e -> e);
end;

#
## _dropCacheTags
#
# drop the cache tag if the problem size fits entirely into cache.
#
_dropCacheTags := (t, size, inplace) ->

    (ObjId(t) <> _ACache())           # let other tags through
    or When(inplace = true,  # inplace?
        ((t.params[2].csize) < size), # only let through ACache tags smaller than problem size
        (t.params[2].csize/2 < size)  # (div 2 accounts for rd/wr)
    )
;

#
## _expandRight
#
_expandRight := function(d, t)
    local tag, leftmax;

    tag := t.getTag(_AExpRight());

    # if the tag isn't present, pass through
    if tag = false then
        return true;
    fi;

    # get the limit on left side size, handle multiple tags
    leftmax := When(IsList(tag),
        Minimum(List(tag, e -> e.params[1])),
        tag.params[1]
    );

    # if left expansion exceeds our max, don't allow this breakdown
    return d[1] <= 2^leftmax;
end;

#
## _allowedExpRight
#
#

_allowedExpRight := (nt) -> let(
    t := nt.getTag(_AExpRight()),
    tt := When(IsList(t), t, [t]),
    maxx := Minimum(List(tt, e -> e.params[1])),
    minn := Maximum(List(tt, e -> When(IsBound(e.params[2]) and nt.params[1] > e.params[2], e.params[2], 0))), # optional 2nd param is minimum
#    debugout := Chain(Print(nt.params[1], ": ", minn, "-", maxx, "\n"), 0),
    [minn..maxx]
);

#
## _dropExpandRight
#
# drop all expandright tags at smallest size.
#
# NOTE: this function returns TRUE if we should keep the tag, and FALSE if the
# tag is to be dropped.
#

_dropExpRightTags := (t, size) ->
    ObjId(t) <> _AExpRight()
    or (
        size > 2^Minimum(t.params)
        and not IsBound(t.params[3]) # the presence of a third param says "drop this tag asap"
    )
;

#
#
## DivisorProds
#
# returns lists made up of DivisorsInt(N) which are \leq M and their product = N
#
Declare(_getdivisors);

DivisorProds := function(N, M)
    local l, r;

    # list of valid divisors.
    l := Filtered(DivisorsInt(N), e -> e <= M and e <> 1);

    r := _getdivisors(N, l, [1]);

    # the last element is a 1, we don't want it in the output.
    return List(r, e -> DropLast(e, 1));
end;

_getdivisors := function(N, list, prod)
    local result, e, p;

    p := Product(prod);

    result := List(
        Filtered(list, e -> e * p = N),
        ee -> Concat([ee], prod)
    );

    for e in Filtered(list, i -> i * p < N) do
        Append(result, _getdivisors(N, list, Concat([e], prod)));
    od;

    return result;
end;

_loop := function(p, i)
    local idx, ii, a, c, size;

    # paranoia, 'i' in range.
    Constraint(i >=1 and i <= Length(p));

    idx := [
        XChain([0]),
        GTPar,
        GTVec,
        XChain([1,0,2])
    ];

    #
    a := Product(p{[1..(i-1)]});
    c := Product(p{[i+1..Length(p)]});

    ii := When(a > 1, 1, 0) + When(c > 1, 2, 0) + 1;

    size := [
        [],
        [a],
        [c],
        [a,c]
    ];

    return [idx[ii], size[ii]];
end;

#
## WHT RULES ###############
#

NewRulesFor(WHT, rec(

    #F WHT_BinSplit:
    #F
    #F same as WHT_GeneralSplit, but only allows for binary splits
    #F
    #F   WHT_(2^k) =
    #F     WHT_(2^k1)   tensor I_(2^(k-k1)) *
    #F     I_(2^(k-k2)) tensor WHT_(2^k2)
    #F
    #F We use this rule only, when WHT_GeneralSplit is turned off.
    #F
    #F NOTE: propagates tags, handles expand-right tag.
    WHT_BinSplit := rec (
        info             := "WHT_(2^k) -> (WHT_(2^k1) tensor I) (I tensor WHT_(2^k2))",
        forTransposition := false,
        minSize          := 2,
        maxRadix         := 32,
        applicable       := (self, nt) >> nt.params[1] >= self.minSize,
        requiredFirstTag := ANoTag,

        children := (self, nt) >> List([1.. Minimum(self.maxRadix, nt.params[1]-1)],
            i -> [ WHT(i), WHT(nt.params[1]-i) ]
        ),

        apply := (nt, c, cnt) ->
            Tensor(c[1], I(Rows(c[2])))
            * Tensor(I(Rows(c[1])), c[2])
    ),

    # handle the right expanded WHT.
    WHT_ExpandRight := rec (
        info             := "WHT_(2^k) -> (WHT_(2^k1) tensor I) (I tensor WHT_(2^k2))",
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.params[1] >= self.minSize and nt.hasTag(_AExpRight()),

        children := nt -> List(Intersection([1..nt.params[1]-1], _allowedExpRight(nt)),
            i -> [
                WHT(i).withTags(
                    Filtered(nt.getTags(), e ->
                        _dropExpRightTags(e, 2^i)
                    )
                ),
                WHT(nt.params[1]-i).withTags(
                    Filtered(nt.getTags(), e ->
                        _dropExpRightTags(e, 2^(nt.params[1]-i))
                    )
                )
            ]
        ),

        apply := (nt, c, cnt) ->
            Tensor(c[1], I(Rows(c[2])))
            * Tensor(I(Rows(c[1])), c[2])
    ),

    # top level bin split rule (requires ATopLevel tag) which inserts some permutations which are
    # linear on the bits into the ruletree.
    # this rule expects ATopLevel param1 to be a size at which to stop
    # propogating the ATopLevel tag and param2 to be a cache specification
    # of the form [E,S,A,R]
    WHT_BinSplitB := rec (
        info             := "WHT_(2^k) -> (WHT_(2^k1) tensor I) (I tensor WHT_(2^k2))",
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.hasTag(ATopLevel) and Rows(nt) > self.minSize,

        children := nt -> let(t := nt.getTag(ATopLevel).params[1],
            List([1..(nt.params[1]-1)], i ->
                When(i >= t or (nt.params[1]-i) >= t,
                    [ WHT(i).withTags([ATopLevel(t)]), WHT(nt.params[1]-i).withTags([ATopLevel(t)]) ],
                    [ WHT(i), WHT(nt.params[1]-i) ]
                )
            )
        ),

        apply := (nt, c, cnt) ->
                Tensor(c[1], I(Rows(c[2])))
                * PushL(
                    CL(Rows(nt), Rows(c[2]), nt.getTag(ATopLevel).params[2]))
                * PushR(
                    CL(Rows(nt), Rows(c[2]), nt.getTag(ATopLevel).params[2]).transpose())
                * Tensor(I(Rows(c[1])), c[2]),

        switch := true
    ),

    WHT_BinSplitB_binloops := rec (
        info             := "WHT_(2^k) -> (WHT_(2^k1) tensor I) (I tensor WHT_(2^k2))",
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.hasTag(ATopLevel) and Rows(nt) > self.minSize,

        children := nt -> let(t := nt.getTag(ATopLevel).params[1],
            List([1..(nt.params[1]-1)], i ->
                When(i >= t or (nt.params[1]-i) >= t,
                    [ WHT(i).withTags([ATopLevel(t)]), WHT(nt.params[1]-i).withTags([ATopLevel(t)]) ],
                    [ WHT(i), WHT(nt.params[1]-i) ]
                )
            )
        ),

        apply := function(nt, c, cnt)
            local a, b;

            a := WHT_BinSplit_binloops.apply(nt, c, cnt);

            b := CL(Rows(nt), Rows(c[2]), nt.getTag(ATopLevel).params[2]);

            if b = I(Rows(nt)) then
                return a;
            else
                return Grp(a._children[1] * b) * Grp(b.transpose() * a._children[2]);
            fi;
        end,

        switch := true,
    ),
    #######################################################################################################
    #   tSPL WHT rule
    # tSPL WHT_(2^k) -> (WHT_(2^k1) tensor I) (I tensor WHT_(2^k2))
    WHT_tSPL_BinSplit := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >>
            nt.hasTags()
            and nt.params[1] >= self.minSize
            and not nt.hasTag(_AInplace()),
        children         := nt -> List([1..nt.params[1] - 1], i -> [ TTensor(WHT(i), WHT(nt.params[1]-i)).withTags(nt.getTags()) ]),
        apply            := (nt, c, cnt) -> c[1],
        switch := true,
    ),

    #F WHT_Base: WHT_1 = F_2
    #F
    WHT_tSPL_Base := rec(
        switch := false,
        applicable := (t) -> Rows(t) = 2 and t.hasTags(),
        children := t -> [[ TTensorI(F(2), 1, AVec, AVec).withTags(t.getTags()) ]],
        apply := (t, C, Nonterms) -> C[1]
    ),

    #######################################################################################################
    #   tSPL Pease WHT rule
    # Pease tSPL WHT_(2^k) -> \Prod((I tensor F_2)L)
    WHT_tSPL_Pease   := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.hasTags()
                                          and nt.params[1] >= self.minSize
                                          and IsBound(nt.firstTag().legal_kernel)
                                          and ForAny(self.radices, i -> nt.firstTag().legal_kernel(2^i)),

        radices          := [1, 2, 3, 4, 5],

        children         := (self, nt) >>
            let(
                k := nt.params[1],
                streamsize := Cond(
                    nt.hasTags() and IsBound(nt.firstTag().bs),
                    nt.firstTag().bs,
                    NULL
                ),
                ap := Filtered(self.radices, n -> IsInt(k/n) and streamsize >= 2^n),
                List(ap, i -> let(
                    r := i,
                    Cond(
                        IsInt(k/r) and nt.hasTags() and IsBound(nt.firstTag().bs)
                            and nt.firstTag().bs >= 2^r,
                        [ TICompose(var("j"), k/r, TTensorI(WHT(i), 2^(k-r), APar, AVec)).withTags(nt.getTags())],
                        []
                    )
                ))
            ),

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

    #######################################################################################################
    #   tSPL Korn-Lambiotte WHT rule
    WHT_tSPL_KornLambiotte   := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.hasTags()
                                and nt.params[1] >= self.minSize
                                and ForAny(self.radices, i->IsInt(nt.params[1]/i)
                                and IsBound(nt.firstTag().legal_kernel)
                                and nt.firstTag().legal_kernel(2^i)
                            ),

        radices          := [1, 2, 3, 4, 5],

        children         := (self, nt) >> let(
                                k := nt.params[1],
                                ap := Filtered(self.radices, n -> IsInt(k/n) and nt.firstTag().legal_kernel(2^n)),
                                List(ap, i ->
                                    [ TICompose(var("j"), k/i,
                                        TTensorI(WHT(i), 2^(k-i), AVec, APar),
                                        nt.getTags()
                                    )]
                                )
                            ),

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

    # WHT GT rule. Direct copy of DFT_GT_CT minus the twiddles
    #
    # WHT_nm -> GT(WHT_n, ...) * GT(WHT_m, ...)
    #
    # supports the ACache tag.
    #
    WHT_GT_CT := rec(
        switch := true,
        maxSize := false,
        minSize := false,
        forTransposition := 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))
            and not t.hasTag(_AInplace()),

        children := (self, t) >> Map2(
            # _divisorCacheTags determines if the split is allowed (according to the cache params)
            Filtered(
                DivisorPairs(Rows(t)),
                d -> _divisorCacheTags(d, t) and _expandRight(d, t)
            ),
            (m,n) -> [
                GT(WHT(Log2Int(m)), XChain([0, 1]), XChain([0, 1]), [n]).withTags(
                    Filtered(t.getTags(), e ->
                        _dropCacheTags(e, m, false) and _dropExpRightTags(e, m)
                    )
                ),
                GT(WHT(Log2Int(n)), XChain([1, 0]), XChain([1, 0]), [m]).withTags(
                    Filtered(t.getTags(), e ->
                        _dropCacheTags(e, n, false) and _dropExpRightTags(e, n)
                    )
                )
            ]
        ),

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

    #
    ## WHT_TopInplace
    #
    # the input and output strides must be the same, and intermediate
    # arrays are *full length*, which means the code generated is not
    # recursive.
    #
    # each 'stage' is <= kernel size.
    #
    WHT_TopInplace := rec(
        switch := true,
        maxSize := false,
        minSize := false,

        applicable := (self, t) >> let(
            n := Rows(t),

            t.hasTag(_AInplace())
            and n > 2
            and (self.maxSize=false or n <= self.maxSize)
            and (self.minSize=false or n >= self.minSize)
            and not IsPrime(n)),

        children := meth(self, t)
            local iptag, divs, wa, w, p, i, loopdata, newtags;

            iptag := t.getTag(_AInplace());

            divs := DivisorProds(
                Rows(t),
                When(iptag <> false, iptag.params[1], Rows(t))
            );

            wa := [];
            newtags := Filtered(t.getTags(), e -> ObjId(e) <> _AInplace());

            for p in divs do
                w := [];
                for i in [1..Length(p)] do
                    loopdata := _loop(p, i);
                    Add(w, Inplace(
                        GT(
                            WHT(Log2Int(p[i])),
                            loopdata[1],
                            loopdata[1],
                            loopdata[2]
                        ).withTags(newtags)
                    ));
                od;
                Add(wa, w);
            od;

            return wa;
        end,

        apply := (self, t, C, Nonterms) >>  ApplyFunc(Compose, C)
    ),

    #
    ## WHT_GT_Inplace
    #
    # standard Binsplit CT rule except in place, tags ignored and not
    # propagated.

    WHT_GT_Inplace := rec(
        switch := true,
        maxSize := false,
        minSize := false,

        applicable := (self, t) >> let(
            n := Rows(t),

            t.hasTag(_AInplace()) <> false
            and n > 2
            and not IsPrime(n)
        ),

        children := (self, t) >> Map2(
            DivisorPairs(Rows(t)),
            (m,n) -> [
                GT(WHT(Log2Int(m)), XChain([0, 1]), XChain([0, 1]), [n]).withTags(t.getTags()),
                GT(WHT(Log2Int(n)), XChain([1, 0]), XChain([1, 0]), [m]).withTags(t.getTags())
            ]
        ),

        apply := (self, t, C, nt) >> Inplace(ApplyFunc(Compose, C))
    )
));

#
## GT-WHT RULES ########################
#
# rules for GT(WHT, ...)
# these rules are a copy of the rules for the DFT

NewRulesFor(GT, rec(
    # copy and simplification of GT_DFT_Base2
    # matches GT(WHT(n), ...) where n = 1
    GT_WHT_Base := rec(
        forTransposition := false,
        switch           := false,
        applicable := (self, t) >> let(
            rank := Length(t.params[4]),                                # rank is number of outer loops

            rank = 0                                                    # means no outer loops
            and PatternMatch(t, [GT, WHT, @, @, @, @, @], empty_cx())   # t.rChildren(), used by patternmatch appends t.transposed and t.tags, so GT, with its 4 params, actually has 6.
            and Rows(t.params[1])=2                                     # we're looking for a WHT of size 2
        ),

        apply := (t, C, Nonterms) -> F(2)
    ),

    # matches GT(WHT(n), ...) where n > 1
    GT_WHT_Inplace := rec(
        maxSize       := false,
        minSize       := false,
        minRank       := 0,
        maxRank       := 3,
        forTransposition := false,
        switch           := false,

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

            t.getTag(_AInplace()) <> false
            and rank >= self.minRank
            and rank <= self.maxRank
            and rank = 0 # MRT: we use NthLoop for rank > 0
            and (self.maxSize=false or Rows(wht) <= self.maxSize)
            and (self.minSize=false or Rows(wht) >= self.minSize)
            and PatternMatch(t, [GT, WHT, XChain, XChain, @, @, @], empty_cx())
            and WHT_GT_CT.applicable(wht)
        ),

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

            Map2(
                DivisorPairs(Rows(wht)),
                (m,n) -> [
                    GT(
                        WHT(Log2Int(m)),
                        s.composeWith(XChain([0, 1])),
                        s.composeWith(XChain([0, 1])),
                        Concatenation([n], loop_dims)
                    ).withTags(Filtered(t.getTags(), e ->
                        ObjId(e) <> _AInplace() or m > e.params[1]
                    )),
                    GT(
                        WHT(Log2Int(n)),
                        g.composeWith(XChain([1, 0])),
                        s.composeWith(XChain([1, 0])),
                        Concatenation([m], loop_dims)
                    ).withTags(Filtered(t.getTags(), e ->
                        ObjId(e) <> _AInplace() or n > e.params[1]
                    ))
                ]
            )
        ),

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

    GT_WHT_CT := rec(
        maxSize       := false,
        minSize       := false,
        minRank       := 0,
        maxRank       := 3,
        codeletSize   := 32,
        forTransposition := false,
        switch           := false,

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

#            not t.hasTag(_ACache())
#            and
            not t.hasTag(_AInplace())
            and not t.hasTag(_ABuf())
            and rank >= self.minRank
            and rank <= self.maxRank
#            and When(rank>0, t.hasTags(), true)
            and rank = 0 # MRT: we use NthLoop for rank > 0
            and (self.maxSize=false or Rows(wht) <= self.maxSize)
            and (self.minSize=false or Rows(wht) >= self.minSize)
            and PatternMatch(t, [GT, WHT, XChain, XChain, @, @, @], empty_cx())
            and WHT_GT_CT.applicable(wht)
        ),

        children := (self, t) >> let(
            wht := t.params[1],
            g := t.params[2],
            s := t.params[3],
            loop_dims := t.params[4],
            nloops := Length(loop_dims),
            tags := t.getTags(),
            inp := t.getTag(_AInplace()) <> false,

            Map2(
                Filtered(
                    DivisorPairs(Rows(wht)),
                    d -> ( d[1] <= self.codeletSize) and _divisorCacheTags(d, t) and _expandRight(d, t)
                ),
                (m,n) -> [
                    GT(
                        WHT(Log2Int(m)),
                        s.composeWith(XChain([0, 1])),
                        s.composeWith(XChain([0, 1])),
                        Concatenation([n], loop_dims)
                    ).withTags(
                        Filtered(t.getTags(), e ->
                            _dropCacheTags(e, m, inp) and _dropExpRightTags(e, m)
                        )
                    ),

                    GT(
                        WHT(Log2Int(n)),
                        g.composeWith(XChain([1, 0])),
                        s.composeWith(XChain([1, 0])),
                        Concatenation([m], loop_dims)
                    ).withTags(
                        Filtered(t.getTags(), e ->
                            _dropCacheTags(e, n, inp) and _dropExpRightTags(e, n)
                        )
                    )
                ]
            )
        ),

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

    GT_NthLoop_PassTags := rec(
        switch := false,

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

            rank > 0 and t.hasTags()
        ),

        # restrict to innermost first (i.e. loop interchange)
        # to search over loop orders use [1..nloops]

        # Limit tag reduces the number of loop interchanges.
        # it is useful when you don't want the number of potential
        # ruletrees to explode, and you are not overtly concerned
        # with having access to all the potential ones.
        freedoms := t -> let(
            fr := [1..Length(t.params[4])],
            When(t.hasTag(ALimitNthLoop),
                [[1]],
                [fr]
            )
        ),

        child := (t, fr) -> let(
#            codeletSize := 32,
            spl := t.params[1],
            g := t.params[2],
            s := t.params[3],
            loopid := fr[1],
            gt := GT(spl, g.without(loopid),
                s.without(loopid), ListWithout(t.params[4], loopid)
            ),
            [
                gt.withTags(t.getTags()),
                # Filtered(t.getTags(), e ->
                #     ObjId(e) <> _AInplace() or Rows(gt) >= e.params[1]
                # )),
                InfoNt(loopid)
            ]
        ),

        apply := (t, C, Nonterms) -> let(
            loopid := Nonterms[2].params[1],
            dft := Nonterms[1].params[1],
            g := t.params[2],
            s := t.params[3],
            loop_dims := t.params[4],
            i := Ind(loop_dims[loopid]),

            ISum(i, Scat(s.part(loopid, i, Rows(dft), loop_dims))
            * C[1]
            * Gath(g.part(loopid, i, Cols(dft), loop_dims)))
        )
    ),
));

#   (WHTmn x Ik) -> (WHTm x Ikn) (Im x (WHTn x Ik))
NewRulesFor(TTensorI, rec(
    WHTxI_vecrec := rec(
        switch           := false,
        forTransposition := false,
        minKernel := false,
        maxKernel := false,
        applicable := (self, nt) >> nt.hasTags() and IsVecVec(nt.params) and
            (not IsInt(self.minKernel) or nt.params[1].dims()[2] > self.minKernel) and
            (not IsInt(self.maxKernel) or nt.params[1].dims()[2] <= self.maxKernel),
        children := nt -> let(mn := nt.params[1].dims()[2], k := nt.params[2],
            List(Flat(List(DivisorPairs(mn), i -> let(m := i[1], n := i[2],
                [
                    TCompose([
                        TTensorI(WHT(Log2Int(m)), n*k, AVec, AVec),
                        TTensorI(TTensorI(WHT(Log2Int(n)), k, AVec, AVec), m, APar, APar)
                    ]).withTags(nt.getTags()),
                    TCompose([
                        TTensorI(TTensorI(WHT(Log2Int(n)), k, AVec, AVec), m, APar, APar),
                        TTensorI(WHT(Log2Int(m)), n*k, AVec, AVec)
                    ]).withTags(nt.getTags()),
                 ])
            )), j->[j])
        ),
        apply := (nt, c, cnt) -> c[1]
    )
));


InstallMethod(MitM_GAPToOMRec,
"for a dihedral perm group",
[IsPermGroup and IsDihedralGroup],
D -> OMA( MitM_SimpleOMS("DihedralGroup")
        , MitM_SimpleOMS("IsPermGroup")
        , MitM_GAPToOMRec(Size(D)) ) );

InstallMethod(MitM_GAPToOMRec,
"for a quaternion perm group",
[IsPermGroup and IsQuaternionGroup],
Q -> OMA( MitM_SimpleOMS("QuaternionGroup"),
          MitM_SimpleOMS("IsPermGroup"),
          MitM_GAPToOMRec(Size(Q))));

InstallMethod(MitM_GAPToOMRec,
"for an integer",
[IsInt],
i -> OMI(i));

InstallMethod(MitM_GAPToOMRec,
"for a float",
[IsFloat],
v -> OMF(v));

InstallMethod(MitM_GAPToOMRec,
"for a string in string representation",
[IsString and IsStringRep],
s -> OMSTR(s));

InstallMethod(MitM_GAPToOMRec,
"for a char",
[IsChar],
c -> OMA( OMS(MitM_cdbase, "prim", "CharConstr")
        , OMSTR( [c] ) ) );

InstallMethod(MitM_GAPToOMRec,
"for a boolean",
[IsBool],
b -> OMA( OMS( MitM_cdbase, "prim", "BoolConstr" )
        , OMSTR( String(b) ) ) );

InstallMethod(MitM_GAPToOMRec,
"for a record",
[IsRecord],
function(r)
    local content, rnam;
    content := [OMS(MitM_cdbase, "prim", "RecConstr")];
    for rnam in Set(RecNames(r)) do
        Add(content, OMSTR(rnam));
        Add(content, MitM_GAPToOMRec(r.(rnam)));
    od;
    return CallFuncList(OMA, content);
end);

InstallMethod(MitM_GAPToOMRec,
              "for an omrec",
              [MitM_OMRec],
              IdFunc);

InstallMethod(MitM_GAPToOMRec,
              "for a permutation",
              [IsPerm],
function(p)
    local content;

    content := [ OMS(MitM_cdbase, "prim", "PermConstr") ];
    Append( content
          , List(ListPerm(p), MitM_GAPToOMRec) );
    return CallFuncList(OMA, content);
end);

InstallMethod(MitM_GAPToOMRec,
"for a polynomial ring",
[IsPolynomialRing],
R -> OMA( MitM_SimpleOMS("PolynomialRing")
        , MitM_GAPToOMRec(LeftActingDomain(R))
        , MitM_GAPToOMRec(List(IndeterminatesOfPolynomialRing(R), String)) ) );

InstallMethod(MitM_GAPToOMRec,
"for a polynomial",
[IsPolynomial],
function(p)
    local content;
    return OMA( MitM_SimpleOMS("PolynomialByExtRep"),
                OMA(MitM_SimpleOMS("RationalFunctionsFamily"),
                    OMA(MitM_SimpleOMS("FamilyObj"),
                        MitM_GAPToOMRec(1))),
                MitM_GAPToOMRec(ExtRepPolynomialRatFun(p)));
end);

InstallMethod(MitM_GAPToOMRec,
"for the ring of integers",
[IsIntegers],
I -> MitM_SimpleOMS("Integers"));

InstallMethod(MitM_GAPToOMRec,
"for a list",
[IsList],
function(L)
    local content, item;
    content := [ OMS(MitM_cdbase
                     , "prim"
                     , "ListConstr") ];
    for item in L do
        Add(content, MitM_GAPToOMRec(item));
    od;
    return CallFuncList(OMA, content);
end);

InstallMethod(MitM_GAPToOMRec,
"for a finite field element",
[IsFFE],
function(elm)
    local content, char, deg, log, coef;
    content := [];
    Add(content, OMS( MitM_cdbase
                    , "prim"
                    , "FFEConstr"));
    char := Characteristic(elm);
    deg := DegreeFFE(elm);
    Add(content, MitM_GAPToOMRec(char));
    Add(content, MitM_GAPToOMRec(deg));
    if IsInternalRep(elm) then
        log := LogFFE(elm, Z(char, deg));
        elm := FFECONWAY.ZNC(char, deg) ^ log;
    fi;
    for coef in elm![1] do
        Add(content, MitM_GAPToOMRec(IntFFE(coef)));
    od;
    return CallFuncList(OMA, content);
end);

# TODO: This is a hack. We look for functions that create objects using
#       `Objectify` by essentially grepping through their source (so this
#       only works for GAP-level functions) and designating any function
#       a constructor that calls Objectify.
#
#       As a first test we wrap these functions in a constructor-memoiser
MitM_C___Wrapper := function(n, callee)
    return function(args...)
        local obj, omacont, a, m;
        obj := CallFuncList(callee, args);

        omacont := [ OMS(MitM_cdbase, "lib", n) ];
        for a in args do
            m := ApplicableMethod(MitM_GAPToOMRec, [ a ]);
            if m <> fail then
                Add(omacont, m(a));
            else
                # CANT DO
                return obj;
            fi;
        od;
        # This will of course only work with attribute storing objects
        SetMitM_GAPToOMRec(obj, CallFuncList(OMA, omacont) );
        return obj;
    end;
end;

# Under no circumstances should we touch these. It's where madness lies.
MitM_ConstructorBlacklist := [ OMOBJ, OMA, OMS, OMATTR, OMSTR, OMF,
                               OMI, OME, OMV ];
InstallGlobalFunction(MitM_FindConstructors,
function()
    local n, v, j, fun, res, a, meths, mpos, mres;

    res := [];
    for n in NamesGVars() do
        if IsBoundGlobal(n) then
            v := ValueGlobal(n);
            if IsOperation(v) then
                for a in [1..6] do
                    meths := METHODS_OPERATION(v, a);
                    j := 0;
                    while j + a + 2 < Length(meths) do
                        fun := meths[j + a + 2];
                        if PositionSublist(String(fun), "Objectify") <> fail then
                            meths[j + a + 2] := MitM_C___Wrapper(n, fun);
                            CHANGED_METHODS_OPERATION(v, a);
                            Add(res, rec( type := "oper", name := n, value := fun, operation := v ));
                        fi;
                        j := j + (BASE_SIZE_METHODS_OPER_ENTRY + a);
                    od;
                od;
            fi;
            if IsFunction(v) then
                if PositionSublist(String(v), "Objectify") <> fail then
                    if not (v in MitM_ConstructorBlacklist) then
                        Info(InfoMitMConstructors, 15, "Overloading ", n, "." );
                        MakeReadWriteGlobal(n);
                        UnbindGlobal(n);
                        BindGlobal(n, MitM_C___Wrapper(n, v));
                        Add(res, rec( type := "gvar", name := n, value := v ));
                    else
                        Info(InfoMitMConstructors, 15, "Skipping ", n, "." );
                    fi;
                fi;
            fi;
        fi;
    od;
    return res;
end);


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

# misc functions for hamlet stuff

# takes the permutation in SPL, returns the bit matrix
# extends some of the Peter's stuff, -BA
SPLtoBitMatrix := function(perm)
	local m,bit;

	m := MatSPL(perm);

	bit := BitMatrixToInts(PermMatrixToBits(m));

	return bit;

end;

SPLtoLocalMemConf := function(perm,w)
	local opts,path,t,filename,cmdString;

	opts := InitStreamUnrollHw();

	path := Concat("/tmp/spiral/", String(GetPid()), "/");
	filename := "perm";
    MakeDir(path);

	BRAMPerm.print := (self,i,is) >> Print(self.name, "Mem(", BitMatrixToInts(self._children[1]), ", ", BitMatrixToInts(self._children[2]), ", ", self.streamSize, ")");

	t := streamGen(perm.withTags([AStream(w)]),opts);

	PrintTo(ConcatenationString(path, filename, ".spl"), HDLPrint(1024, t.dims(), -1, t));

    if (_splhdl_path = "") then
		Error("Error: path to SPLHDL compiler not set: paradigms.stream._splhdl_path is not bound.");
    fi;

    cmdString := ConcatenationString(_splhdl_path, " ", path, filename, ".spl");

	Exec(cmdString);

	BRAMPerm.print := (self,i,is) >> Print(self.name, "(", BitMatrixToInts(self._children[1]), ", ", BitMatrixToInts(self._children[2]), ", ", self.streamSize, ")");

	return path;
end;


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

reps:=[[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1]],              # E_8
#       [[1,1],[2,1],[10,1],[11,1],[5,1],[6,1],[7,1],[8,1]],            # E_8(a_1)
       [[1,1],[2,1],[10,1],[11,1],[5,1],[6,1],[7,1],[8,1]],            # E_8(a_1)
#       [[1,1],[2,1],[3,1],[10,1],[12,1],[13,1],[14,1],[8,1]],          # E_8(a_2)
       [[1,1],[2,1],[3,1],[10,1],[12,1],[13,1],[14,1],[8,1]],          # E_8(a_2)
       [[1,1],[4,1],[6,1],[7,1],[8,1],[17,1],[18,1],[19,1]],           # E_8(a_3) e cel de mai jos dar ordonat
#       [[8,1],[18,1],[7,1],[6,1],[19,1],[1,1],[17,1],[4,1]],           # E_8(a_3)  merge si in char 5
#       [[7,1],[8,1],[9,1],[10,1],[11,1],[12,1],[13,1],[19,1]],         # E_8(a_3)
       [[1,1],[4,1],[6,1],[8,1],[17,1],[18,1],[19,1],[21,1]],          # E_8(a_4) e cel de mai jos dar ordonat
#       [[18,1],[6,1],[19,1],[1,1],[17,1],[21,1],[4,1],[8,1]],          # E_8(a_4) merge si in char 5
#       [[9,1],[10,1],[11,1],[12,1],[13,1],[14,1],[15,1],[19,1]],       # E_8(a_4)
       [[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1]],                    # E_7
       [[1,1],[4,1],[7,1],[8,1],[14,1],[17,1],[19,1],[26,1]],          # E_8(b_4) e cel de mai jos dar ordonat
#       [[8,1],[7,1],[14,1],[26,1],[19,1],[1,1],[17,1],[4,1]],          # E_8(b_4) merge si in char 5
#       [[7,1],[8,1],[9,1],[17,1],[18,1],[19,1],[20,1],[21,1]],         # E_8(b_4)
#       [[18,1],[22,1],[19,1],[1,1],[17,1],[20,1],[21,1],[4,1],[6,1]],  # E_8(a_5)
       [[1,1],[12,1],[14,1],[15,1],[17,1],[21,1],[26,1],[27,1]],       # E_8(a_5) D_8(a_1) in a D_8: deduced from Seitz' E_8(a_6) used in proof
#       [[9,1],[14,1],[15,1],[17,1],[18,1],[19,1],[20,1],[26,1]],       # E_8(a_5)
#       [[1,1],[2,1],[11,1],[10,1],[5,1],[6,1],[7,1]],                  # E_7(a_1)
       [[1,1],[3,1],[10,1],[11,1],[5,1],[6,1],[7,1]],                  # E_7(a_1)
#       [[18,1],[8,1],[31,1],[7,1],[33,1],[4,1],[19,1],[23,1]],         # E_8(b_5) # works in char 5
       [[4,1],[7,1],[8,1],[18,1],[19,1],[23,1],[31,1],[33,1]],         # E_8(b_5) # e cel de sus ordonat
#       [[8,1],[16,1],[17,1],[18,1],[20,1],[21,1],[24,1],[33,1]],       # E_8(b_5)
       [[8,1],[7,1],[6,1],[5,1],[4,1],[2,1],[3,1]],                    # D_7
#       [[18,1],[31,1],[7,1],[33,1],[4,1],[29,1],[23,1],[19,1]],        # E_8(a_6) # works in char 5
       [[4,1],[7,1],[18,1],[19,1],[23,1],[29,1],[31,1],[33,1]],        # E_8(a_6) # e cel de sus ordonat
#       [[4,1],[10,1],[12,1],[14,1],[15,1],[26,1],[27,1],[30,1]],       # E_8(a_6)
#       [[1,1],[2,1],[3,1],[10,1],[12,1],[13,1],[14,1]],                # E_7(a_2)
       [[1,1],[2,1],[3,1],[10,1],[12,1],[13,1],[14,1]],                # E_7(a_2)
       [[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[8,1]],                    # E_6A_1

#      [[2,1],[3,1],[10,1],[12,1],[6,1],[7,1],[8,1],[83,1]],            # (D_7(a_1))^{(2)}    char 2
#       [[8,1],[7,1],[6,1],[5,1],[10,1],[11,-1],[2,1],[3,1]],            # D_7(a_1)
       [[2,1],[3,1],[10,1],[12,1],[6,1],[7,1],[8,1]],                   # D_7(a_1)
#       [[4,1],[15,1],[18,1],[19,1],[23,1],[31,1],[33,1],[39,1]],       # E_8(b_6) # deduced from E_8(b_5) ?gp de componente?
       [[4,1],[8,1],[18,1],[19,1],[23,1],[31,1],[33,1],[39,-1],[41,1]], # E_8(b_6) # cel de mai jos ordonat
#       [[31,1],[33,1],[18,1],[4,1],[39,-1],[41,1],[8,1],[19,1],[23,1]], # E_8(b_6) # cu - merge si in char 5
#       [[4,1],[17,1],[12,1],[29,1],[30,1],[31,1],[34,1],[35,1]],        # E_8(b_6) Seitz' used in proof
#       [[15,1],[22,1],[23,1],[24,1],[25,1],[26,1],[27,1],[28,1]],       # E_8(b_6)
       [[1,1],[4,1],[6,1],[7,1],[17,1],[18,1],[19,1]],                  # E_7(a_3) e cel de mai jos dar ordonat
#       [[17,1],[1,1],[19,1],[6,1],[7,1],[18,1],[4,1]],                  # E_7(a_3) merge si in char 5
#       [[1,1],[4,1],[7,1],[12,1],[13,1],[17,1],[19,1]],                 # E_7(a_3)
#       [[3,1],[5,1],[1,1],[6,1],[11,1],[12,1],[2,1],[8,1]],             # E_6(a_1)A_1
       [[1,1],[2,1],[11,1],[12,1],[5,1],[6,1],[8,1]],                   # E_6(a_1)+A_1
#       [[1,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[67,1]],             # (A_7)^{(3)}     char 3
       [[1,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1]],                     # A_7
#       [[8,1],[7,1],[13,1],[10,1],[11,-1],[12,1],[2,1],[3,1]],          # D_7(a_2)
       [[2,1],[3,1],[10,1],[12,1],[13,1],[14,1],[8,1]],                 # D_7(a_2)
       [[1,1],[2,1],[3,1],[4,1],[5,1],[6,1]],                           # E_6
       [[7,1],[6,1],[5,1],[4,1],[2,1],[3,1]],                           # D_6
#       [[1,1],[2,1],[3,1],[4,1],[5,1],[7,1],[8,1],[51,1]],             # (D_5+A_2)^{(2)}     char 2
       [[1,1],[3,1],[4,1],[2,1],[5,1],[7,1],[8,1]],                     # D_5A_2
#       [[3,1],[5,1],[1,1],[6,1],[11,1],[12,1],[2,1]],                   # E_6(a_1)
       [[1,1],[2,1],[11,1],[12,1],[5,1],[6,1]],                         # E_6(a_1)
       [[1,1],[4,1],[7,1],[14,1],[17,1],[18,1],[19,1],[26,1]],          # E_7(a_4) e cel de mai jos dar ordonat
#       [[17,1],[1,1],[19,1],[14,1],[26,1],[7,1],[18,1],[4,1]],          # E_7(a_4) merge si in char 5
#       [[1,1],[4,1],[7,1],[11,1],[25,1],[26,1],[27,1]],                 # E_7(a_4)
       [[2,1],[4,1],[5,1],[6,1],[7,1],[8,1],[1,1]],                     # A_6A_1
#       [[7,1],[6,1],[5,1],[10,1],[11,-1],[2,1],[3,1]],                  # D_6(a_1)
       [[2,1],[3,1],[10,1],[12,1],[6,1],[7,1]],                         # D_6(a_1)
       [[2,1],[4,1],[5,1],[6,1],[7,1],[8,1]],                           # A_6
#       [[47,1],[49,1],[5,1],[45,1],[44,1],[42,1],[27,1],[46,1]],        # E_8(a_7)
       [[30,1],[33,1],[35,1],[36,1],[40,1],[45,1],[53,1],[58,1]],       # E_8(a_7)

       [[1,1],[3,1],[4,1],[2,1],[5,1],[7,1]],                # D_5A_1
#       [[24,1],[26,1],[7,1],[27,1],[23,1],[4,1],[25,1]],     # E_7(a_5)
       [[4,1],[7,1],[23,1],[24,1],[25,1],[26,1],[27,1]],     # E_7(a_5) e cel de sus ordonat
#       [[4,1],[11,1],[14,1],[16,1],[24,1],[25,1],[26,1]],    # E_7(a_5)
       [[1,1],[4,1],[6,1],[8,1],[17,1],[18,1],[19,1]],       # E_6(a_3)A_1  e cel de mai jos dar ordonat
#       [[17,1],[1,1],[19,1],[6,1],[18,1],[4,1],[8,1]],       # E_6(a_3)A_1 merge si in char 5
#       [[4,1],[6,1],[9,1],[11,1],[18,1],[19,1],[8,1]],       # E_6(a_3)+A_1
#       [[7,1],[13,1],[10,1],[11,-1],[12,1],[2,1],[3,1]],     # D_6(a_2)
       [[2,1],[3,1],[10,1],[12,1],[13,1],[14,1]],            # D_6(a_2)
#       [[1,1],[3,1],[10,1],[12,1],[2,1],[5,1],[7,1],[8,1]],  # D_5(a_1)A_2
       [[1,1],[10,1],[11,1],[2,1],[5,1],[7,1],[8,1]],        # D_5(a_1)+A_2
       [[2,1],[4,1],[5,1],[6,1],[7,1],[1,1]],                # A_5A_1
       [[1,1],[3,1],[4,1],[2,1],[6,1],[7,1],[8,1]],          # A_4A_3
       [[1,1],[3,1],[4,1],[2,1],[5,1]],                      # D_5
       [[1,1],[4,1],[6,1],[17,1],[18,1],[19,1]],             # E_6(a_3)  e cel de mai jos dar ordonat
#       [[17,1],[1,1],[19,1],[6,1],[18,1],[4,1]],             # E_6(a_3) merge si in char 5
#       [[4,1],[6,1],[9,1],[11,1],[18,1],[19,1]],             # E_6(a_3)
#       [[2,1],[3,1],[4,1],[5,1],[7,1],[8,1],[63,1]],        # (D_4+A_2)^{(2)}     char 2
       [[3,1],[4,1],[2,1],[5,1],[7,1],[8,1]],                # D_4A_2
       [[5,1],[6,1],[7,1],[8,1],[1,1],[3,1],[2,1]],          # A_4A_2A_1
#       [[1,1],[3,1],[10,1],[12,1],[2,1],[5,1],[7,1]],        # D_5(a_1)A_1
       [[1,1],[10,1],[11,1],[2,1],[5,1],[7,1]],              # D_5(a_1)+A_1
       [[1,1],[3,1],[4,1],[5,1],[6,1]],                      # A_5
       [[1,1],[3,1],[4,1],[2,1],[6,1],[7,1]],                # A_4A_2
       [[1,1],[3,1],[4,1],[2,1],[6,1],[8,1]],                # A_4A_1^2
#       [[1,1],[3,1],[10,1],[12,1],[2,1],[5,1]],              # D_5(a_1)
       [[1,1],[10,1],[11,1],[2,1],[5,1]],                    # D_5(a_1)

       [[1,1],[3,1],[4,1],[6,1],[7,1],[8,1]],          # A_3^2
       [[1,1],[3,1],[4,1],[2,1],[6,1]],                # A_4A_1
#       [[3,1],[10,1],[12,1],[2,1],[5,1],[7,1],[8,1]],  # D_4(a_1)A_2
       [[2,1],[3,1],[10,1],[12,1],[7,1],[8,1]],        # D_4(a_1)+A_2
       [[3,1],[4,1],[2,1],[5,1],[7,1]],                # D_4A_1
       [[5,1],[6,1],[7,1],[1,1],[3,1],[2,1]],          # A_3A_2A_1
       [[1,1],[3,1],[4,1],[2,1]],                      # A_4
#       [[1,1],[3,1],[4,1],[6,1],[7,1],[44,1]],        # (A_3+A_2)^{(2)}     char 2
       [[3,1],[4,1],[2,1],[6,1],[7,1]],                # A_3A_2
#       [[3,1],[10,1],[12,1],[2,1],[5,1],[7,1]],        # D_4(a_1)A_1
       [[2,1],[3,1],[10,1],[12,1],[7,1]],              # D_4(a_1)+A_1 Ross'
#       [[2,1],[5,1],[10,1],[11,1],[7,1]],              # D_4(a_1)+A_1 Seitz'
       [[5,1],[6,1],[7,1],[2,1],[3,1]],                # A_3A_1^2
       [[1,1],[3,1],[5,1],[6,1],[2,1],[8,1]],          # A_2^2A_1^2
       [[3,1],[4,1],[2,1],[5,1]],                      # D_4
#       [[3,1],[10,1],[12,1],[2,1],[5,1]],              # D_4(a_1)
       [[2,1],[3,1],[10,1],[12,1]],                    # D_4(a_1)
       [[1,1],[3,1],[4,1],[6,1]],                      # A_3A_1
       [[1,1],[3,1],[5,1],[6,1],[2,1]],                # A_2^2A_1
       [[1,1],[3,1],[5,1],[6,1]],                      # A_2^2
       [[1,1],[3,1],[2,1],[5,1],[7,1]],                # A_2A_1^3
       [[1,1],[3,1],[4,1]],                            # A_3
       [[2,1],[4,1],[1,1],[6,1]],                      # A_2A_1^2
       [[1,1],[3,1],[2,1]],                            # A_2A_1
       [[2,1],[3,1],[5,1],[7,1]],                      # A_1^4
       [[1,1],[3,1]],                                  # A_2
       [[1,1],[4,1],[6,1]],                            # A_1^3
       [[1,1],[6,1]],                                  # A_1^2
       [[1,1]]                                         # A_1
       ];

I:=[[],
    [4],           #??? E_8(a_1)
    [4,6],         #??? E_8(a_2)
    [2,3,5],       # E_8(a_3)
    [2,3,5,7],     # E_8(a_4)
    [],            # E_7
    [2,3,5,6],     # E_8(b_4)
    [2,3,5,6,8],   # E_8(a_5)
    [4],           # E_7(a_1)
    [1,2,3,5,6],   # E_8(b_5)
    [],            # D_7
    [1,2,3,5,6,8], # E_8(a_6)
    [4,6],         #??? E_7(a_2)
    [],            # E_6A_1

#    [4],             # (D_7(a_1))^{(2)}     char 2
    [4],             # D_7(a_1)
    [1,2,3,5,6,7],   # E_8(b_6)
    [2,3,5],         # E_7(a_3)
    [4],             # E_6(a_1)A_1
#    [],              # (A_7)^{(3)}     char 3
    [],              # A_7
    [4,5],           #??? D_7(a_2)
    [],              # E_6
    [],              # D_6
#    [],              # (D_5+A_2)^{(2)}     char 2
    [],              # D_5A_2
    [4],             # E_6(a_1)
    [2,3,5,6],       # E_7(a_4)
    [],              # A_6A_1
    [4],             # D_6(a_1)
    [],              # A_6
    [1,2,3,4,6,7,8], # E_8(a_7)

    [],          # D_5A_1
    [1,2,3,5,6], # E_7(a_5)
    [2,3,5],     # E_6(a_3)A_1
    [4,5],       #??? D_6(a_2)
    [4],         # D_5(a_1)A_2
    [],          # A_5A_1
    [],          # A_4A_3
    [],          # D_5
    [2,3,5],     # E_6(a_3)
#    [],          # (D_4+A_2)^{(2)}     char 2
    [],          # D_4A_2
    [],          # A_4A_2A_1
    [4],         # D_5(a_1)A_1
    [],          # A_5
    [],          # A_4A_2
    [],          # A_4A_1
    [4],         # D_5(a_1) check this also

    [],  # A_3^2
    [],  # A_4A_1
    [4], # D_4(a_1)A_2
    [],  # D_4A_1
    [],  # A_3A_2A_1
    [],  # A_4
#    [],  # (A_3+A_2)^{(2)}     char 2
    [],  # A_3A_2
    [4], # D_4(a_1)A_1
    [],  # A_3A_1^2
    [],  # A_2^2A_1^2
    [],  # D_4
    [4], # D_4(a_1)
    [],  # A_3A_1
    [],  # A_2^2A_1
    [],  # A_2^2
    [],  # A_2A_1^3
    [],  # A_3
    [],  # A_2A_1^2
    [],  # A_2A_1
    [],  # A_1^4
    [],  # A_2
    [],  # A_1^3
    [],  # A_1^2
    []   # A_1
    ];

#
# The parabolic where the representative is distinguished
#
II:=[[1,2,3,4,5,6,7,8], # E_8
     [1,2,3,4,5,6,7,8], # E_8(a_1)
     [1,2,3,4,5,6,7,8], # E_8(a_2)
     [1,2,3,4,5,6,7,8], # E_8(a_3)
     [1,2,3,4,5,6,7,8], # E_8(a_4)
     [1,2,3,4,5,6,7],   # E_7
     [1,2,3,4,5,6,7,8], # E_8(b_4)
     [1,2,3,4,5,6,7,8], # E_8(a_5)
     [1,2,3,4,5,6,7],   # E_7(a_1)
     [1,2,3,4,5,6,7,8], # E_8(b_5)
     [2,3,4,5,6,7,8],   # D_7
     [1,2,3,4,5,6,7,8], # E_8(a_6)
     [1,2,3,4,5,6,7],   # E_7(a_2)
     [1,2,3,4,5,6,8],   # E_6A_1

#     [2,3,4,5,6,7,8],   # D_7(a_1)^{(2)}     char 2
     [2,3,4,5,6,7,8],   # D_7(a_1)
     [1,2,3,4,5,6,7,8], # E_8(b_6)
     [1,2,3,4,5,6,7],   # E_7(a_3)
     [1,2,3,4,5,6,8],   # E_6(a_1)A_1
#     [1,3,4,5,6,7,8],   # A_7^{(3)}     char 3
     [1,3,4,5,6,7,8],   # A_7
     [2,3,4,5,6,7,8],   # D_7(a_2)
     [1,2,3,4,5,6],     # E_6
     [2,3,4,5,6,7],     # D_6
#     [1,2,3,4,5,7,8],   # D_5A_2^{(2)}     char 2
     [1,2,3,4,5,7,8],   # D_5A_2
     [1,2,3,4,5,6],     # E_6(a_1)
     [1,2,3,4,5,6,7],   # E_7(a_4)
     [1,2,4,5,6,7,8],   # A_6A_1
     [2,3,4,5,6,7],     # D_6(a_1)
     [2,4,5,6,7,8],     # A_6
     [1,2,3,4,5,6,7,8], # E_8(a_7)

     [1,2,3,4,5,7],   # D_5A_1
     [1,2,3,4,5,6,7], # E_7(a_5)
     [1,2,3,4,5,6,8], # E_6(a_3)A_1
     [2,3,4,5,6,7],   # D_6(a_2)
     [1,2,3,4,5,7,8], # D_5(a_1)A_2
     [1,2,4,5,6,7],   # A_5A_1
     [1,2,3,4,6,7,8], # A_4A_3
     [1,2,3,4,5],     # D_5
     [1,2,3,4,5,6],   # E_6(a_3)
#     [2,3,4,5,7,8],   # D_4A_2^{(2)}     char 2
     [2,3,4,5,7,8],   # D_4A_2
     [1,2,3,5,6,7,8], # A_4A_2A_1
     [1,2,3,4,5,7],   # D_5(a_1)A_1
     [1,3,4,5,6],     # A_5
     [1,2,3,4,6,7],   # A_4A_2
     [1,2,3,4,6,8],   # A_4A_1^2
     [1,2,3,4,5],     # D_5(a_1)

     [1,3,4,6,7,8], # A_3^2
     [1,2,3,4,6],   # A_4A_1
     [2,3,4,5,7,8], # D_4(a_1)A_2
     [2,3,4,5,7],   # D_4A_1
     [1,2,3,5,6,7], # A_3A_2A_1
     [1,2,3,4],     # A_4
#     [2,3,4,6,7],   # A_3A_2^{(2)}     char 2
     [2,3,4,6,7],   # A_3A_2
     [2,3,4,5,7],   # D_4(a_1)A_1
     [2,3,5,6,7],   # A_3A_1^2
     [1,2,3,5,6,8], # A_2^2A_1^2
     [2,3,4,5],     # D_4
     [2,3,4,5],     # D_4(a_1)
     [1,3,4,6],     # A_3A_1
     [1,2,3,5,6],   # A_2^2A_1
     [1,3,5,6],     # A_2^2
     [1,2,3,5,7],   # A_2A_1^3
     [1,3,4],       # A_3
     [1,2,4,6],     # A_2A_1^2
     [1,2,3],       # A_2A_1
     [2,3,5,7],     # A_1^2
     [1,3],         # A_2
     [1,4,6],       # A_1^3
     [1,6],         # A_1^2
     [1]            # A_1
     ];

labels:=["E_8",
         "E_8(a_1)",
         "E_8(a_2)",
         "E_8(a_3)",
         "E_8(a_4)",
         "E_7",
         "E_8(b_4)",
         "E_8(a_5)",
         "E_7(a_1)",
         "E_8(b_5)",
         "D_7",
         "E_8(a_6)",
         "E_7(a_2)",
         "E_6A_1",

#         "D_7(a_1)^{(2)}",
         "D_7(a_1)",
         "E_8(b_6)",
         "E_7(a_3)",
         "E_6(a_1)A_1",
#         "A_7^{(3)}",
         "A_7",
         "D_7(a_2)",
         "E_6",
         "D_6",
#         "D_5A_2^{(2)}",
         "D_5A_2",
         "E_6(a_1)",
         "E_7(a_4)",
         "A_6A_1",
         "D_6(a_1)",
         "A_6",
         "E_8(a_7)",

         "D_5A_1",
         "E_7(a_5)",
         "E_6(a_3)A_1",
         "D_6(a_2)",
         "D_5(a_1)A_2",
         "A_5A_2",
         "A_4A_3",
         "D_5",
         "E_6(a_3)",
#         "D_4A_2^{(2)}",
         "D_4A_2",
         "A_4A_2A_1",
         "D_5(a_1)A_1",
         "A_5",
         "A_4A_2",
         "A_4A_1^2",
         "D_5(a_1)",

         "A_3^2",
         "A_4A_1",
         "D_4(a_1)A_2",
         "D_4A_1",
         "A_3A_2A_1",
         "A_4",
#         "A_3A_2^{(2)}",
         "A_3A_2",
         "D_4(a_1)A_1",
         "A_3A_1^2",
         "A_2^2A_1^2",
         "D_4",
         "D_4(a_1)",
         "A_3A_1",
         "A_2^2A_1",
         "A_2^2",
         "A_2A_1^3",
         "A_3",
         "A_2A_1^2",
         "A_2A_1",
         "A_1^4",
         "A_2",
         "A_1^3",
         "A_1^2",
         "A_1"
         ];

tori:=[
    [2,2,2,2,2,2,2,2],   # E_8
    [2,2,2,0,2,2,2,2],   # E_8(a_1)
    [2,2,2,0,2,0,2,2],   # E_8(a_2)
    [2,0,0,2,0,2,2,2],   # E_8(a_3)
    [2,0,0,2,0,2,0,2],   # E_8(a_4)
    [2,2,2,2,2,2,2,-27], # E_7
    [2,0,0,2,0,0,2,2],   # E_8(b_4)
    [2,0,0,2,0,0,2,0],   # E_8(a_5)
    [2,2,2,0,2,2,2,-21], # E_7(a_1)
    [0,0,0,2,0,0,2,2],   # E_8(b_5)
    [-21,2,2,2,2,2,2,2], # D_7
    [0,0,0,2,0,0,2,0],   # E_8(a_6)
    [2,2,2,0,2,0,2,-17], # E_7(a_2)
    [2,2,2,2,2,2,-17,2], # E_6A_1

#    [-16,2,0,2,2,2,2,2],   # D_7(a_1)^{(2)}     char 2
    [-16,2,2,0,2,2,2,2],   # D_7(a_1)
    [0,0,0,2,0,0,0,2],     # E_8(b_6)
    [2,0,0,2,0,2,2,-15],   # E_7(a_3)
    [2,2,2,0,2,2,-13,2],   # E_6(a_1)A_1
#    [2,-15,2,2,2,2,2,2],   # A_7^{(3)}     char 4
    [2,-15,2,2,2,2,2,2],   # A_7
    [-13,2,2,0,2,0,2,2],   # D_7(a_2)
    [2,2,2,2,2,2,-16,0],   # E_6
    [-15,2,2,2,2,2,2,-10], # D_6
#    [2,2,2,2,2,-12,2,2],   # D_5A_2^{(2)}     char 2
    [2,2,2,2,2,-12,2,2],   # D_5A_2
    [2,2,2,0,2,2,-12,0],   # E_6(a_1)
    [2,0,0,2,0,0,2,-11],   # E_7(a_4)
    [2,2,-11,2,2,2,2,2],   # A_6(a_1)
    [-11,2,2,0,2,2,2,-8],  # D_6(a_1)
    [0,2,-10,2,2,2,2,2],   # A_6
    [0,0,0,0,2,0,0,0],     # E_8(a_7)

    [2,2,2,2,2,-11,2,-1], # D_5A_1
    [0,0,0,2,0,0,2,-9],   # E_7(a_5)
    [2,0,0,2,0,2,-9,2],   # E_6(a_3)A_1
    [-9,2,2,0,2,0,2,-6],  # D_6(a_2)
    [2,2,2,0,2,-9,2,2],   # D_5(a_1)A_2
    [2,2,-9,2,2,2,2,-5],  # A_5A_1
    [2,2,2,2,-9,2,2,2],   # A_4A_3
    [2,2,2,2,2,-10,0,0],  # D_5
    [2,0,0,2,0,2,-8,0],   # E_6(a_3)
#    [-6,2,2,2,2,-8,2,2],  # D_4A_2^{(2)}     char 2
    [-6,2,2,2,2,-8,2,2],  # D_4A_2
    [2,2,2,-7,2,2,2,2],   # A_4A_2A_1
    [2,2,2,0,2,-8,2,-1],  # D_5(a_1)A_1
    [2,-9,2,2,2,2,-5,0],  # A_5
    [2,2,2,2,-8,2,2,-2],  # A_4A_2
    [2,2,2,2,-7,2,-2,2],  # A_4A_1^2
    [2,2,2,0,2,-7,0,0],    # D_5(a_1)

    [2,-3,2,2,-6,2,2,2],   # A_3^2
    [2,2,2,2,-7,2,-1,0],   # A_4A_1
    [-4,2,2,0,2,-6,2,2],   # D_4(a_1)A_2
    [-6,2,2,2,2,-7,2,-1],  # D_4A_1
    [2,2,2,-6,2,2,2,-3],   # A_3A_2A_1
    [2,2,2,2,-6,0,0,0],    # A_4
#    [-3,2,2,2,-6,2,2,-2],  # A_3A_2^{(2)}     char 2
    [-3,2,2,2,-6,2,2,-2],  # A_3A_2
    [-4,2,2,0,2,-5,2,-1],  # D_4(a_1)A_1
    [-1,2,2,-5,2,2,2,-3],  # A_3A_1^2
    [2,2,2,-5,2,2,-3,2],   # A_2^2A_1^2
    [-6,2,2,2,2,-6,0,0],   # D_4
    [-4,2,2,0,2,-4,0,0],   # D_4(a_1)
    [2,-3,2,2,-4,2,-1,0],  # A_3A_1
    [2,2,2,-5,2,2,-2,0],   # A_2^2A_1
    [2,0,2,-4,2,2,-2,0],   # A_2^2
    [2,2,2,-4,2,-2,2,-1],  # A_2A_1^3
    [2,-3,2,2,-3,0,0,0],   # A_3
    [2,2,-3,2,-3,2,-1,0],  # A_2A_1^2
    [2,2,2,-3,0,0,0,0],    # A_2A_1
    [-1,2,2,-3,2,-2,2,-1], # A_1^4
    [2,0,2,-2,0,0,0,0],    # A_2
    [2,-1,-2,2,-2,2,-1,0], # A_1^3
    [2,0,-1,0,-1,2,-1,0],  # A_1^2
    [2,0,-1,0,0,0,0,0]     # A_1
    ];

#blocks:=[];
blocks:=[
    [[51, 1], [47, 1], [35, 1], [25, 4], [15, 1]],
    [[25, 9], [23, 1]],
    [[25, 7], [23, 1], [20, 2], [5, 2]],
    [[25, 5], [23, 2], [21, 1], [15, 2], [10, 2], [3, 2]],
    [[25, 3], [23, 1], [20, 2], [15, 4], [10, 4], [5, 2]],
    [[25, 4], [24, 2], [22, 2], [15, 2], [10, 2], [3, 1], [1, 3]],
    [[25, 2], [23, 1], [21, 1], [19, 1], [17, 2], [15, 2], [13, 1], [11, 3], [7, 2], [5, 1], [3, 2]],
    [[23, 2], [21, 1], [19, 1], [17, 1], [15, 3], [13, 2], [11, 3], [10, 2], [7, 1], [5, 1], [3, 3]],
    [[25, 2], [22, 2], [19, 1], [17, 1], [16, 2], [15, 1], [12, 2], [11, 2], [7, 1], [6, 2], [3, 1], [1, 3]],
    [[23, 1], [19, 2], [17, 3], [15, 3], [11, 1], [10, 4], [9, 1], [7, 2], [5, 1], [3, 4]],
    [[23, 1], [22, 2], [19, 1], [16, 2], [15, 1], [13, 3], [12, 2], [11, 1], [10, 2], [7, 1], [4, 2], [3, 1], [1, 3]],
    [[19, 2], [15, 5], [11, 1], [10, 8], [9, 1], [5, 7]],
    [[23, 1], [19, 1], [18, 2], [17, 1], [16, 2], [15, 2], [11, 1], [10, 4], [8, 2], [7, 1], [4, 2], [3, 2], [1, 3]],
    [[23, 1], [18, 2], [17, 3], [16, 2], [15, 1], [11, 1], [10, 2], [9, 3], [8, 2], [3, 2], [2, 4], [1, 3]],
    [[19, 1], [17, 2], [15, 3], [13, 1], [11, 3], [10, 6], [7, 2], [5, 4], [3, 3], [1, 1]],
    [[17, 1], [15, 3], [13, 2], [11, 3], [10, 6], [7, 3], [5, 8], [3, 2]],
    [[19, 1], [17, 1], [16, 2], [15, 2], [12, 2], [11, 2], [10, 6], [7, 1], [6, 2], [5, 3], [3, 1], [2, 2], [1, 3]],
    [[17, 1], [15, 1], [14, 2], [13, 2], [12, 2], [11, 1], [10, 4], [9, 2], [8, 2], [7, 1], [5, 7], [3, 2], [2, 2], [1, 1]],
    [[16, 2], [15, 1], [13, 3], [12, 2], [11, 1], [10, 2], [9, 3], [8, 4], [7, 1], [5, 7], [3, 1], [1, 3]],
    [[15, 1], [14, 2], [11, 1], [10, 10], [9, 2], [5, 15], [1, 1]],
    [[23, 1], [17, 7], [15, 1], [11, 1], [9, 7], [3, 1], [1, 14]],
    [[19, 1], [16, 4], [15, 1], [11, 6], [10, 4], [6, 4], [5, 2], [1, 10]],
    [[15, 1], [13, 2], [11, 3], [10, 8], [9, 1], [7, 2], [5, 11], [3, 5], [1, 1]],
    [[17, 1], [15, 1], [13, 6], [11, 1], [10, 2], [9, 6], [7, 1], [5, 7], [3, 1], [1, 8]],
    [[15, 1], [13, 1], [12, 2], [11, 2], [10, 8], [8, 2], [7, 1], [5, 10], [4, 2], [3, 2], [2, 2], [1, 3]],
    [[13, 3], [12, 2], [11, 1], [10, 2], [9, 3], [8, 4], [7, 5], [6, 4], [5, 3], [4, 2], [3, 2], [2, 2], [1, 3]],
    [[15, 1], [12, 4], [11, 1], [10, 6], [9, 4], [7, 1], [5, 10], [3, 5], [1, 6]],
    [[13, 3], [11, 5], [9, 3], [7, 13], [5, 3], [3, 5], [1, 6]],
    [[11, 1], [10, 6], [9, 3], [7, 5], [5, 20], [3, 5]],
    [[15, 1], [12, 2], [11, 5], [10, 4], [9, 3], [8, 2], [5, 10], [3, 1], [2, 6], [1, 6]],
    [[11, 1], [10, 6], [9, 1], [8, 4], [7, 2], [6, 2], [5, 18], [4, 2], [3, 3], [1, 3]],
    [[11, 1], [10, 4], [9, 3], [8, 4], [7, 3], [6, 2], [5, 17], [3, 3], [2, 4], [1, 3]],
    [[11, 1], [10, 6], [8, 4], [7, 5], [5, 17], [4, 4], [3, 1], [1, 6]],
    [[11, 1], [10, 2], [9, 3], [8, 6], [7, 4], [6, 4], [5, 10], [4, 2], [3, 7], [2, 4], [1, 3]],
    [[11, 1], [10, 4], [9, 3], [8, 2], [7, 4], [6, 6], [5, 13], [4, 2], [3, 1], [2, 4], [1, 6]],
    [[5, 48], [4, 2]],
    [[15, 1], [11, 9], [9, 7], [5, 10], [1, 21]],
    [[11, 1], [10, 2], [9, 7], [7, 7], [5, 17], [3, 2], [1, 14]],
    [[11, 1], [9, 6], [7, 14], [5, 7], [3, 14], [1, 8]],
    [[5, 46], [4, 2], [3, 2], [2, 2]],
    [[11, 1], [9, 3], [8, 6], [7, 8], [6, 6], [5, 3], [4, 2], [3, 7], [2, 10], [1, 6]],
    [[11, 1], [10, 2], [9, 7], [6, 14], [5, 9], [4, 2], [1, 17]],
    [[5, 46], [3, 5], [1, 3]],
    [[5, 45], [3, 4], [2, 4], [1, 3]],
    [[11, 1], [9, 1], [8, 8], [7, 8], [6, 8], [5, 1], [3, 8], [2, 8], [1, 15]],
    [[5, 38], [4, 12], [1, 10]],
    [[5, 45], [3, 1], [2, 6], [1, 8]],
    [[5, 36], [3, 20], [1, 8]],
    [[11, 1], [8, 6], [7, 14], [6, 6], [3, 2], [2, 14], [1, 21]],
    [[5, 32], [4, 8], [3, 10], [2, 10], [1, 6]],
    [[5, 45], [1, 23]],
    [[5, 30], [4, 8], [3, 13], [2, 8], [1, 11]],
    [[5, 29], [4, 6], [3, 14], [2, 14], [1, 9]],
    [[5, 25], [4, 10], [3, 14], [2, 14], [1, 13]],
    [[5, 18], [4, 12], [3, 20], [2, 20], [1, 10]],
    [[11, 1], [7, 26], [3, 1], [1, 52]],
    [[5, 29], [3, 25], [1, 28]],
    [[5, 21], [4, 16], [3, 9], [2, 14], [1, 24]],
    [[5, 14], [4, 14], [3, 23], [2, 18], [1, 17]],
    [[5, 14], [3, 50], [1, 28]],
    [[5, 7], [4, 14], [3, 28], [2, 28], [1, 17]],
    [[5, 13], [4, 32], [1, 55]],
    [[5, 3], [4, 16], [3, 27], [2, 32], [1, 24]],
    [[5, 1], [4, 12], [3, 32], [2, 32], [1, 35]],
    [[4, 8], [3, 28], [2, 48], [1, 36]],
    [[5, 1], [3, 55], [1, 78]],
    [[4, 2], [3, 27], [2, 52], [1, 55]],
    [[3, 14], [2, 64], [1, 78]],
    [[3, 1], [2, 56], [1, 133]]
    ];

#ords:=[];
ords:=[ 125, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 5, 25, 25,  25, 5, 25, 25, 5, 5, 25, 5, 5, 5, 25, 5, 5, 5, 5, 5, 5, 25, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 ];

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


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

# Don't forget to Import(platforms.cellSPU)

#F _svctSizes(N, p, v). Uptil N, multiples of p, vectorsize=v
#F Eg: spu_4x32f_medium uses _svctSizes(1024, 16, 4);
_svctSizes := (N, p, v) -> Filtered([1..N], i->ForAll(Factors(i), j->j<=p) and IsInt(i/v^2));

#F _svctSizes(N, p, v, from). Uptil N, multiples of p, vectorsize=v, beginning with from
#F Eg: spu_4x32f_medium uses _svctSizes(1024, 16, 4, 96);
_svctSizesFrom := (N, p, v, low) -> Filtered([low..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)));

Class(cellopts, rec(
#    ic := rec(oddSizes := false, svct := true, tsplRader:=false, tsplBluestein:=false,
#              splitL := false, pushTag := true, flipIxA := false,
#              stdTTensor := true, tsplPFA := false, interleavedComplex := true),
#
#    sc := rec(oddSizes := false, svct := true, tsplRader:=false, tsplBluestein:=false,
#              splitL := false, pushTag := true, flipIxA := false,
#              stdTTensor := true, tsplPFA := false, interleavedComplex := false)
sc := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := false),
ic := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := true),
));

Class(cellOpts, rec(
            2x64f := rec(
                dft_sc := rec(
                    small :=  rec(verify:=true, tsplBluestein:=false, interleavedComplex := false, PRDFT:=true, URDFT:= true, cplxVect := true, stdTTensor := false, globalUnrolling:=10000),
                    medium := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := false),
                ),
                dft_ic := rec(
                    small := rec(verify:=true, tsplBluestein:=false, interleavedComplex := true, PRDFT:=true, URDFT:= true, cplxVect := true, stdTTensor := false, globalUnrolling:=10000),
                    medium := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := true),
                )
            ),
            4x32f := rec(
                dft_sc := rec(
                    small := rec(verify:=true, interleavedComplex := false, stdTTensor := false, globalUnrolling:=10000),
                    medium := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := false),
                ),
                dft_ic := rec(
                    small := rec(verify:=true, interleavedComplex := true, stdTTensor := false, globalUnrolling:=10000),
                    medium := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := true),
                )
            )
));

benchSPU := function()
    if LocalConfig.cpuinfo.SIMD().hasSPU() then
        return rec(
            2x64f := rec(
                1d := rec(
                    dft_sc := rec(
                        small := _defaultSizes(s->doSimdDft(s, spu_2x64f, cellOpts.2x64f.split.small), [ 2..32 ]),
                        medium := _defaultSizes(s->doSimdDft(s, spu_2x64f, cellOpts.2x64f.split.medium), _svctSizes(1024, 16, 2)),
#                        medium := spiral.libgen.doParSimdDft(spu_2x64f, 1, _svctSizes(1024, 16, 4), false, true, false, false),
#                        large := spiral.libgen.doParSimdDft(spu_2x64f, 1, List([4..20], i->2^i), false, true, false, false)

                    ),
                    dft_ic := rec(
                        small := _defaultSizes(s->doSimdDft(s, spu_2x64f,  cellOpts.2x64f.dft_ic.small), [2..32]),
                        medium := _defaultSizes(s->doSimdDft(s, spu_2x64f, cellOpts.2x64f.dft_ic.medium), _svctSizes(1024, 16, 2)),
#                        medium := spiral.libgen.doParSimdDft(spu_2x64f, 1, _svctSizes(1024, 16, 4), false, true, false, true),
#                        large := spiral.libgen.doParSimdDft(spu_2x64f, 1, List([4..20], i->2^i), false, true, false, true)
                    ),
                    rdft := _defaultSizes(s->doSimdSymDFT(TRDFT, s, spu_2x64f, rec(verify :=true)), 32*[1..12]),
                    dht  := _defaultSizes(s->doSimdSymDFT(TDHT,  s, spu_2x64f, rec(verify :=true)), 8*[1..12]),
                    dct2 := _defaultSizes(s->doSimdSymDFT(TDCT2, s, spu_2x64f, rec(verify :=true)), 32*[1..12]),
                    dct3 := _defaultSizes(s->doSimdSymDFT(TDCT3, s, spu_2x64f, rec(verify :=true)), 32*[1..12]),
                    dct4 := _defaultSizes(s->doSimdSymDFT(TDCT4, s, spu_2x64f, rec(verify :=true)), 32*[1..12]),
                    dst2 := _defaultSizes(s->doSimdSymDFT(TDST2, s, spu_2x64f, rec(verify :=true)), 8*[1..12]),
                    dst3 := _defaultSizes(s->doSimdSymDFT(TDST3, s, spu_2x64f, rec(verify :=true)), 8*[1..12]),
                    dst4 := _defaultSizes(s->doSimdSymDFT(TDST4, s, spu_2x64f, rec(verify :=true)), 8*[1..12])
                ),
                2d := rec(
                    dft_ic := rec(
                        medium := _defaultSizes(s->doSimdMddft(s, spu_2x64f, rec(interleavedComplex := true,
                                    oddSizes := false, svct := true, splitL := false, pushTag := true, flipIxA := false, stdTTensor := true, tsplPFA := false)),
                                    4*List([1..16], i->[i,i])),
                        small := _defaultSizes(s->doSimdMddft(s, spu_2x64f, rec(verify:=true, interleavedComplex := true, globalUnrolling:=10000,
                                    tsplPFA := false, pushTag:= false, oddSizes := true, svct := true, splitL := false)),
                                    List([2..16], i->[i,i]))
                    ),
                    dft_sc := rec(
                        medium := _defaultSizes(s->doSimdMddft(s, spu_2x64f, rec(interleavedComplex := false,
                                    oddSizes := false, svct := true, splitL := false, pushTag := true, flipIxA := false, stdTTensor := true, tsplPFA := false)),
                                    4*List([1..16], i->[i,i])),
                        small := _defaultSizes(s->doSimdMddft(s, spu_2x64f, rec(verify:=true, interleavedComplex := false, globalUnrolling:=10000,
                                    tsplPFA := false, pushTag:= false, oddSizes := true, svct := true, splitL := false)),
                                    List([2..16], i->[i,i]))
                    ),
                    dct2 := _defaultSizes(s->doSimdSymMDDFT(DCT2, s, spu_2x64f, rec(verify := true)), [4, 8, 12, 16]),
                    dct3 := _defaultSizes(s->doSimdSymMDDFT(DCT3, s, spu_2x64f, rec(verify := true)), [4, 8, 12, 16]),
                    dct4 := _defaultSizes(s->doSimdSymMDDFT(DCT4, s, spu_2x64f, rec(verify := true)), [4, 8, 12, 16]),
                    dst2 := _defaultSizes(s->doSimdSymMDDFT(DST2, s, spu_2x64f, rec(verify := true)), [4, 8, 12, 16]),
                    dst3 := _defaultSizes(s->doSimdSymMDDFT(DST3, s, spu_2x64f, rec(verify := true)), [4, 8, 12, 16]),
                    dst4 := _defaultSizes(s->doSimdSymMDDFT(DST4, s, spu_2x64f, rec(verify := true)), [4, 8, 12, 16])
                ),
            ),
            4x32f := rec(
                1d := rec(
                    dft_sc := rec(
                        small := _defaultSizes(s->doSimdDft(s, spu_4x32f, cellOpts.4x32f.dft_sc.small), [ 2..64 ]),
                        medium := _defaultSizes(s->doSimdDft(s, spu_4x32f, cellOpts.4x32f.dft_sc.medium), _svctSizes(1024, 16, 4)),
#                        medium := spiral.libgen.doParSimdDft(spu_4x32f, 1, _svctSizes(1024, 16, 4), false, true, false, false),
#                        large := spiral.libgen.doParSimdDft(spu_4x32f, 1, List([4..20], i->2^i), false, true, false, false)

                    ),
                    dft_ic := rec(
                        small := _defaultSizes(s->doSimdDft(s, spu_4x32f, cellOpts.4x32f.dft_ic.small), [ 2..64 ]),
                        medium := _defaultSizes(s->doSimdDft(s, spu_4x32f, cellOpts.4x32f.dft_ic.medium), _svctSizes(1024, 16, 4))
#                        medium := spiral.libgen.doParSimdDft(spu_4x32f, 1, _svctSizes(1024, 16, 4), false, true, false, true),
#                        large := spiral.libgen.doParSimdDft(spu_4x32f, 1, List([4..20], i->2^i), false, true, false, true)
                    ),
                    rdft := _defaultSizes(s->doSimdSymDFT(TRDFT, s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dht  := _defaultSizes(s->doSimdSymDFT(TDHT,  s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dct2 := _defaultSizes(s->doSimdSymDFT(TDCT2, s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dct3 := _defaultSizes(s->doSimdSymDFT(TDCT3, s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dct4 := _defaultSizes(s->doSimdSymDFT(TDCT4, s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dst2 := _defaultSizes(s->doSimdSymDFT(TDST2, s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dst3 := _defaultSizes(s->doSimdSymDFT(TDST3, s, spu_4x32f, rec(verify :=true)), 32*[1..12]),
                    dst4 := _defaultSizes(s->doSimdSymDFT(TDST4, s, spu_4x32f, rec(verify :=true)), 32*[1..12])
                ),

                2d := rec(
                    dft_ic := rec(
                        medium := _defaultSizes(s->doSimdMddft(s, spu_4x32f, rec(interleavedComplex := true,
                                    oddSizes := false, svct := true, splitL := false, pushTag := true, flipIxA := false, stdTTensor := true, tsplPFA := false)),
                                    16*List([1..5], i->[i,i])),
                        small := _defaultSizes(s->doSimdMddft(s, spu_4x32f, rec(verify:=true, interleavedComplex := true, globalUnrolling:=10000,
                                    tsplPFA := false, pushTag:= false, oddSizes := true, svct := true, splitL := false)),
                                    List([2..16], i->[i,i]))
                    ),
                    dft_sc := rec(
                        medium := _defaultSizes(s->doSimdMddft(s, spu_4x32f, rec(interleavedComplex := false,
                                    oddSizes := false, svct := true, splitL := false, pushTag := true, flipIxA := false, stdTTensor := true, tsplPFA := false)),
                                    16*List([1..5], i->[i,i])),
                        medium128 := _defaultSizes(s->doSimdMddft(s, spu_4x32f, rec(interleavedComplex := false,
                                    oddSizes := false, svct := true, splitL := false, pushTag := true, flipIxA := false, stdTTensor := true, tsplPFA := false)),
                                    16*List([4], i->[8,i])),
                        small := _defaultSizes(s->doSimdMddft(s, spu_4x32f, rec(verify:=true, interleavedComplex := false, globalUnrolling:=10000,
                                    tsplPFA := false, pushTag:= false, oddSizes := true, svct := true, splitL := false)),
                                    List([2..16], i->[i,i]))
                    ),
                    dct2 := _defaultSizes(s->doSimdSymMDDFT(DCT2, s, spu_4x32f, rec(verify := true)), [4, 8, 12, 16]),
                    dct3 := _defaultSizes(s->doSimdSymMDDFT(DCT3, s, spu_4x32f, rec(verify := true)), [4, 8, 12, 16]),
                    dct4 := _defaultSizes(s->doSimdSymMDDFT(DCT4, s, spu_4x32f, rec(verify := true)), [4, 8, 12, 16]),
                    dst2 := _defaultSizes(s->doSimdSymMDDFT(DST2, s, spu_4x32f, rec(verify := true)), [4, 8, 12, 16]),
                    dst3 := _defaultSizes(s->doSimdSymMDDFT(DST3, s, spu_4x32f, rec(verify := true)), [4, 8, 12, 16]),
                    dst4 := _defaultSizes(s->doSimdSymMDDFT(DST4, s, spu_4x32f, rec(verify := true)), [4, 8, 12, 16])
                )
            ),
        );
    else
        return false;
    fi;
end;