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

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

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

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

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

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

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

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

O.sums := self >> self;

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

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

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

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

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

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

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

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

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

# Operators

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Class(SIMD_Macro, SIMD_ISA, rec(

    file := "macro",

    commonIncludes := self >> [],

    active       := true,

    isFixedPoint := false,
    isFloat      := true,
    ctype        := "double",
    bits         := 64,
    useDeref     := false,

    splopts      := rec(precision := "double"),

    duploadn := (y, x, n) -> Error("broadcast is undefined"),

    arrayDataModifier := "static ",
    arrayBufModifier  := "static ",

    declareConstants := true,
    threeOps         := true,
    fma		     := true,

    svload  := [],
    svstore := [],

    unparser := MACROUnparser,

    compileStrategy := self >> BaseIndicesCS
                               :: When(self.fma, [DoFMA], [])
                               :: [ MarkDefUse, #
                                    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
                                    MarkDefUse, #
                                    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true)))]
                               :: When(self.threeOps, [DoThreeOp], [])
                               :: [ Compile.declareVars,
                                    (c, opts) -> opts.vector.isa.fixProblems(c, opts),
                                    HashConsts ],

));

Class(MACRO_2xf, SIMD_Macro, rec(

    info := "MACRO 2 x floating point",

    countrec := rec(
        ops := [
            [ add, sub ]
             :: [ add_cmd, sub_cmd ],
	    [ mul, vmulcx_2xf ]
	     :: [ mul_cmd, vmulcx_2xf_cmd ],
            [ fma, fms, nfma ]
             :: [fma_cmd, fms_cmd, nfma_cmd],
            [ vunpacklo_2xf, vunpackhi_2xf, vpacklo_2xf, vpackhi_2xf, vswapcx_2xf ]
             :: [ vunpacklo_2xf_cmd, vunpackhi_2xf_cmd, vpacklo_2xf_cmd, vpackhi_2xf_cmd, vswapcx_2xf_cmd ],
            [  ],
            [ deref, nth, vload_cmd, vstore_cmd ],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[fmas]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    v     := 2,
    t     := TVect(TReal, 2),

    instr := [vunpacklo_2xf, vunpackhi_2xf, vpacklo_2xf, vpackhi_2xf],

    includes := self >> self.commonIncludes() :: ["<include/macro_fv2.h>"],

    dupload  := (y, x) -> assign(y, vdup(x, 2)),

    mul_cx  := (self, opts) >> ((y,x,c) -> assign(y, vmulcx_2xf(x,c))),
    swap_cx := (y, x, opts) -> assign(y, vswapcx_2xf(x)),
));

Class(MACRO_4xf, SIMD_Macro, rec(

    info := "MACRO 4 x floating point",

    countrec := rec(
        ops := [
            [ add, sub ]
             :: [ add_cmd, sub_cmd ],
	    [ mul, vmulcx_4xf ]
	     :: [ mul_cmd, vmulcx_4xf_cmd ],
            [ fma, fms, nfma ]
             :: [fma_cmd, fms_cmd, nfma_cmd],
            [   vpacklo_4xf,   vpackhi_4xf,   vpacklo2_4xf,   vpackhi2_4xf,
              vunpacklo_4xf, vunpackhi_4xf, vunpacklo2_4xf, vunpackhi2_4xf,
              vswapcx_4xf ]
             :: [   vpacklo_4xf_cmd,   vpackhi_4xf_cmd,   vpacklo2_4xf_cmd,   vpackhi2_4xf_cmd,
              vunpacklo_4xf_cmd, vunpackhi_4xf_cmd, vunpacklo2_4xf_cmd, vunpackhi2_4xf_cmd,
              vswapcx_4xf_cmd ],
            [  ],
            [ deref, nth, vload_cmd, vstore_cmd ],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[fmas]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    v     := 4,
    t     := TVect(TReal, 4),

    instr    := [vpacklo_4xf,   vpackhi_4xf,   vpacklo2_4xf,   vpackhi2_4xf,
                 vunpacklo_4xf, vunpackhi_4xf, vunpacklo2_4xf, vunpackhi2_4xf],

    includes := self >> self.commonIncludes() :: ["<include/macro_fv4.h>"],

    dupload  := (y, x) -> assign(y, vdup(x, 4)),

    mul_cx := (self, opts) >> ((y,x,c) -> assign(y, vmulcx_4xf(x,c))),
    swap_cx := (y, x, opts) -> assign(y, vswapcx_4xf(x)),
));

Class(MACRO_8xf, SIMD_Macro, rec(

    info := "MACRO 8 x floating point",

    countrec := rec(
        ops := [
            [ add, sub ]
             :: [ add_cmd, sub_cmd ],
	    [ mul, vmulcx_4xf ]
	     :: [ mul_cmd, vmulcx_4xf_cmd ],
            [ fma, fms, nfma ]
             :: [fma_cmd, fms_cmd, nfma_cmd],
            [   vpacklo_8xf,   vpackhi_8xf,   vpacklo2_8xf,   vpackhi2_8xf,
              vunpacklo_8xf, vunpackhi_8xf, vunpacklo2_8xf, vunpackhi2_8xf,
              vswapcx_8xf, vtrcx_8xf ]
             :: [ vpacklo_8xf_cmd,   vpackhi_8xf_cmd,   vpacklo2_8xf_cmd,   vpackhi2_8xf_cmd,
              vunpacklo_8xf_cmd, vunpackhi_8xf_cmd, vunpacklo2_8xf_cmd, vunpackhi2_8xf_cmd,
              vswapcx_8xf_cmd, vtrcx_8xf_cmd ],
            [  ],
            [ deref, nth, vload_cmd, vstore_cmd ],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[fmas]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    v     := 8,
    t     := TVect(TReal, 8),

    instr := [  vpacklo_8xf,   vpackhi_8xf,   vpacklo2_8xf,   vpackhi2_8xf,
              vunpacklo_8xf, vunpackhi_8xf, vunpacklo2_8xf, vunpackhi2_8xf,
              vtrcx_8xf ],

    includes := self >> self.commonIncludes() :: ["<include/macro_fv8.h>"],

    dupload  := (y, x) -> assign(y, vdup(x, 8)),

    mul_cx := (self, opts) >> ((y,x,c) -> assign(y, vmulcx_8xf(x,c))),
    swap_cx := (y, x, opts) -> assign(y, vswapcx_8xf(x)),
));

SIMD_ISA_DB.addISA(MACRO_2xf);
SIMD_ISA_DB.addISA(MACRO_4xf);
SIMD_ISA_DB.addISA(MACRO_8xf);


#
# Interprets GAP MathInTheMiddle OpenMath into GAP functions
#

# Make records indicating success or failure
InstallGlobalFunction(MitM_Error,
function(str...)
    return rec(success := false, error := Concatenation(str));
end);

InstallGlobalFunction(MitM_Result,
function(obj)
    return rec(success := true, result := obj);
end);

InstallMethod(MitM_OMRecToGAP, [MitM_OMRecRep],
function(r)
    local val, res;
    val := MitM_IsValidOMRec(r);
    if val <> true then
        return MitM_Error(val);
    fi;
    res := MitM_OMRecToGAPNC(r);
    if res.success <> true then
        return res;
    fi;
    return MitM_Result(EvalString(res.result));
end);

InstallGlobalFunction(MitM_OMRecToGAPNC,
function(r)
    return MitM_EvalToFunction.(MitM_Tag(r))(r);
end);

# Primitives for GAP
BindGlobal("MitM_GAP_PrimitivesFunc", rec(
     ListConstr := "({args...} -> args)",
     RecConstr := "(function(args...) local r,i; r := rec(); for i in [1,3..Length(args)-1] do r.(args[i]) := args[i+1]; od; return r; end)",
     PermConstr := "({args...} -> PermList(args))",
     # TODO: PermBytesConstr
     BoolConstr := "({args...} -> EvalString(args[1]))",
     CharConstr := "({args...} -> args[1][1])",
     FFEConstr := "({args...} -> Sum([0..args[2]-1], i -> args[i+3] * Z(args[1], args[2]) ^ i))",
) );

MitM_Evaluate := function(node)
    return node;
end;

MitM_get_allowed_heads := function()
    return OMA( OMS( "scscp1", "symbol_set" )
              , OMS( "scscp_transient_1", "MitM_Evaluate" )
              , OMS( "scscp_transient_1", "MitM_Quit" )
              , OMS( MitM_cdbase, "lib", "MitM_Evaluate" ) );
end;

BindGlobal("MitM_CDDirectory",
rec( ( "default" ) := function(node)
       if MitM_CD(node) = "scscp2" then
           if MitM_Name(node) = "get_allowed_heads" then
               return MitM_Result( "MitM_get_allowed_heads" );
           else
               return MitM_Error("name \"", MitM_Name(node), "\" not supported");
           fi;
       elif MitM_CD(node) = "scscp_transient_1" then
           if MitM_Name(node) = "MitM_Evaluate" then
               return MitM_Result("MitM_Evaluate");
           elif MitM_Name(node) = "MitM_Quit" then
               QUIT_GAP(0);
           fi;
       else
           return MitM_Error("cd \"", MitM_CD(node), "\" not supported");
       fi;
     end,
     ( MitM_cdbase ) := function(node)
         local name;
         name := MitM_Attributes(node).name;
         if MitM_CD(node) = "prim" then
             if not name in RecNames(MitM_GAP_PrimitivesFunc) then
                 return MitM_Error("OMS name \"", name,
                                   "\" not a GAP primitive function");
             fi;
             return MitM_Result(MitM_GAP_PrimitivesFunc.(name));
         elif MitM_CD(node) = "lib" then
             if not IsBoundGlobal(name) then
                 return MitM_Error("OMS name \"", name,
                                   "\" not a GAP library function");
             fi;
             return MitM_Result(name);
         else
             return MitM_Error("cd \"", MitM_CD(node)
                               , "\" not supported");
         fi;
     end,
) );

# Evaluators for OpenMath objects
InstallValue(MitM_EvalToFunction, rec(
     OMOBJ := function(node)
         return MitM_OMRecToGAPNC(MitM_Content(node)[1]);
     end,

     OMS := function(node)
         local name, sym;

         if MitM_CDBase(node) = fail then
             return MitM_CDDirectory.("default")(node);
         elif IsBound(MitM_CDDirectory.(MitM_CDBase(node))) then
             return MitM_CDDirectory.(MitM_CDBase(node))(node);
         else
             return MitM_Error("cdbase \"", MitM_CDBase(node)
                               , "\" is not supported");
         fi;
     end,

     OMV := node -> MitM_Result(StringFormatted("{}",
                                                MitM_Name(node))),
     OMI := function(node)
         local v;
         v := ShallowCopy(MitM_Content(node)[1]);
         RemoveCharacters(v, WHITESPACE);
         if Position(MitM_Content(node)[1], 'x') <> fail then
             RemoveCharacters(v, "x");
             return MitM_Result(String(IntHexString(v)));
         else
             return MitM_Result(v);
         fi;
     end,

     OMB := node -> MitM_Result(StringFormatted("\"{}\"", MitM_Content(node)[1])),

     OMSTR := function(node)
         local str;
         if IsEmpty(MitM_Content(node)) then
             str := "";
         else
             str := MitM_Content(node)[1];
         fi;
         return MitM_Result(StringFormatted("({} -> \"{}\")()", "{}", str));
     end,

     OMF := function(node)
         if IsBound(MitM_Attributes(node).dec) then
             return MitM_Result(String(Float(MitM_Attributes(node).dec)));
         else
             return MitM_Error("OMF: hex encoding not supported");
         fi;
     end,

     OMA := function(node)
         local sym, args, item, r;

         sym := MitM_OMRecToGAPNC(MitM_Content(node)[1]);
         if sym.success <> true then
             return MitM_Error("OMA contents: ", sym.error);
         fi;
         args := [];
         for item in MitM_Content(node){[2..Length(MitM_Content(node))]} do
             r := MitM_OMRecToGAPNC(item);
             if r.success <> true then
                 return MitM_Error("OMA contents: ", r.error);
             elif MitM_Name(item) = "OMV" then
                 # Interpret string as variable
                 Add(args, EvalString(r.result));
             else
                 Add(args, r.result);
             fi;
         od;
         return MitM_Result(Concatenation(sym.result, "(",
                                          JoinStringsWithSeparator(args, ","),
                                          ")"));
     end,

     OMBIND := function(node)
         local list, val;

         if not (MitM_CD(MitM_Content(node)[1]) = "prim" and
                 MitM_Name(MitM_Content(node)[1]) = "lambda") then
             return MitM_Error("only the lambda binding is implemented");
         fi;
         list := List(MitM_Content(MitM_Content(node)[2]), MitM_Name);
         val := MitM_OMRecToGAPNC(MitM_Content(node)[3]);
         if val.success <> true then
             return MitM_Error("OMBIND contents: ", val.error);
         fi;
         return MitM_Result(Concatenation( "function(",
                                           JoinStringsWithSeparator(list, ","),
                                           ") return ",
                                           val.result,
                                           ";  end" ));
     end,

     OMATTR := function(node)
         return MitM_OMRecToGAPNC(MitM_Content(node)[2]);
     end,

     OME := function(node)
         # TODO: Error handling?
         Print("Error: ", List(MitM_Content(node){[2..Length(MitM_Content(node))]},
                               MitM_OMRecToGAPNC), "\n");
     end,

#     OMR := function(node)
#     end,
    ) );


while true do
    a := Random(0, 19);
    Print(a);
    if a = 10 then
        Print("\n");
        break;
    fi;
    a := Random(0, 19);
    Print("\t", a, "\n");
od;

# 11      6
# 5       8
# 1       4
# 5       10
# 1       16
# 10


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

mizuno:=[[1,2,3,4,5,6,7,
          9,10,11,12,
          13,14,16,17,
          19,18,20,21,
          23,24,25,27,
          26,28,30,31,
          32,33,35,34,
          37,38,39,40,
          41,44,45,46,
          48,49,51,52,
          53,55,57,58,
          59,61,63,64,
          66,69,70,71,
          75,76,80,82,
          85,89,93,97],
         [8,
          15,22,29,36,
          43,42,47,50,
          54,56,60,62,
          65,67,68,72,
          73,74,77,78,
          79,81,83,84,
          86,87,88,90,
          91,92,94,95,
          96,98,99,100,
          101,102,103,104,
          105,106,107,108,
          109,110,111,112,
          114,113,115,116,
          117,118,119,120]];

reps:=[[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[101,1]],          # E_8
       [[1,1],[2,1],[9,1],[10,1],[5,1],[6,1],[7,1],[101,1]],         # E_8(a_1)
       [[1,1],[3,1],[2,1],[9,1],[11,1],[12,1],[13,1],[101,1]],       # E_8(a_2)
       [[8,1],[9,1],[10,1],[11,1],[16,1],[12,1],[7,1],[101,1]],      # E_7+A_1
       [[1,1],[15,1],[16,1],[17,1],[6,1],[7,1],[101,1]],             # E_7
       [[8,1],[9,1],[10,1],[11,1],[16,1],[12,1],[13,1],[102,1]],     # D_8
       [[8,1],[15,1],[16,1],[17,1],[18,1],[19,1],[7,1],[101,1]],     # E_7(a_1)+A_1
       [[1,1],[15,1],[16,1],[17,1],[24,1],[13,1],[101,1]],           # E_7(a_1)
       [[1,1],[15,1],[16,1],[17,1],[18,1],[24,1],[13,1],[102,1]],    # D_8(a_1)       ?
       [[1,1],[15,1],[16,1],[17,1],[18,1],[19,1],[103,1]],           # D_7
       [[14,1],[15,1],[21,1],[17,1],[18,1],[29,1],[19,1],[101,1]],   # E_7(a_2)+A_1
       [[20,1],[21,1],[17,1],[23,1],[24,1],[7,1],[101,1]],           # E_7(a_2)
       [[14,1],[20,1],[21,1],[22,1],[18,1],[33,1],[13,1],[102,1]],   # A_8
       [[20,1],[21,1],[28,1],[23,1],[24,1],[7,1],[101,1]],           # E_6+A_1
       [[14,1],[22,1],[23,1],[24,1],[19,1],[25,1],[35,1],[102,1]],   # D_7(a_1)
       [[14,1],[22,1],[23,1],[24,1],[19,1],[25,1],[102,1]],          # (D_7(a_1))_2
       [[20,1],[21,1],[23,1],[24,1],[7,1],[101,1]],                  # E_6
       [[20,1],[21,1],[22,1],[23,1],[24,1],[25,1],[102,1],[103,1]],  # D_8(a_2)
       [[8,1],[28,1],[23,1],[24,1],[25,1],[30,1],[102,1]],           # D_6+A_1
       [[20,1],[21,1],[28,1],[23,1],[24,1],[10,1],[40,1],[103,1]],   # A_7
#       [[20,1],[21,1],[28,1],[23,1],[24,1],[19,1],[103,1]],          # (A_7)_3
       [[20,1],[21,1],[28,1],[23,1],[31,1],[101,1],[102,1]],         # E_6(a_1)+A_1
       [[1,1],[28,1],[29,1],[30,1],[31,1],[101,1]],                  # D_6
       [[20,1],[21,1],[28,1],[29,1],[25,1],[31,1],[103,1]],          # D_7(a_2)
       [[20,1],[21,1],[23,1],[31,1],[101,1],[102,1]],                # E_6(a_1)
       [[26,1],[28,1],[27,1],[29,1],[30,1],[31,1],[102,1],[103,1]],  # D_5+A_2
       [[26,1],[28,1],[27,1],[29,1],[30,1],[31,1],[102,1]],          # (D_5+A_2)_2
       [[20,1],[21,1],[28,1],[29,1],[30,1],[31,1],[105,1]],          # A_6+A_1
       [[28,1],[27,1],[29,1],[30,1],[31,1],[39,1],[102,1]],          # D_6(a_1)+A_1
       [[28,1],[29,1],[33,1],[31,1],[34,1],[101,1]],                 # D_6(a_1)
       [[29,1],[21,1],[20,1],[30,1],[31,1],[105,1]],                 # A_6
#       [[32,1],[33,1],[30,1],[32,1],[40,1],[101,1]],                 # D_5+A_1        ??? two times 32
       [[26,1],[29,1],[35,1],[38,1],[30,1],[44,1],[47,1],[105,1]],   # 2A_4           ? 26
       [[32,1],[37,1],[29,1],[31,1],[34,1],[105,1],[107,1]],         # A_5+A_2
       [[32,1],[37,1],[27,1],[33,1],[40,1],[36,1],[105,1]],          # A_5+2A_1
       [[32,1],[29,1],[31,1],[34,1],[106,1],[108,1]],                # D_6(a_2)
       [[30,1],[31,1],[32,1],[33,1],[101,1]],                        # D_5
       [[37,1],[33,1],[40,1],[34,1],[36,1],[41,1],[105,1]],          # D_5(a_1)+A_2   ? 26/20
       [[26,1],[27,1],[40,1],[41,1],[106,1],[107,1]],                # (A_5+A_1)'
       [[32,1],[27,1],[37,1],[33,1],[36,1],[105,1]],                 # (A_5+A_1)''
       [[37,1],[38,1],[40,1],[49,1],[41,1],[104,1],[108,1]],         # D_4+A_2
       [[37,1],[38,1],[40,1],[49,1],[41,1],[104,1]],                 # (D_4+A_2)_2
       [[37,1],[38,1],[40,1],[34,1],[36,1],[106,1],[107,1]],         # A_4+A_3
       [[20,1],[21,1],[49,1],[106,1],[107,1]],                       # A_5
       [[37,1],[42,1],[43,1],[44,1],[45,1],[103,1]],                 # D_5(a_1)+A_1
       [[37,1],[38,1],[39,1],[40,1],[41,1],[108,1],[109,1]],         # A_4+A_2+A_1

       [[38,1],[40,1],[39,1],[41,1],[108,1],[109,1]],       # A_4+A_2
       [[42,1],[43,1],[48,1],[49,1],[102,1]],               # D_5(a_1)
       [[47,1],[48,1],[49,1],[53,1],[101,1]],               # D_4+A_1
       [[42,1],[43,1],[44,1],[45,1],[107,1],[108,1]],       # A_4+2A_1
       [[47,1],[48,1],[49,1],[101,1]],                      # D_4
       [[42,1],[48,1],[49,1],[106,1],[107,1]],              # A_4+A_1
       [[37,1],[38,1],[39,1],[49,1],[108,1],[113,1]],       # 2A_3
       [[42,1],[48,1],[106,1],[107,1]],                     # A_4
       [[42,1],[44,1],[48,1],[49,1],[110,1],[113,1]],       # D_4(a_1)+A_2
       [[46,1],[47,1],[48,1],[49,1],[112,1],[113,1]],       # A_3+A_2+A_1
       [[46,1],[47,1],[48,1],[112,1],[116,1],[119,1]],      # A_3+A_2
       [[46,1],[47,1],[48,1],[112,1],[116,1]],              # (A_3+A_2)_2
       [[53,1],[47,1],[48,1],[115,1],[116,1]],              # D_4(a_1)+A_1
       [[53,1],[54,1],[55,1],[112,1],[113,1]],              # A_3+2A_1
       [[47,1],[48,1],[115,1],[116,1]],                     # D_4(a_1)
       [[53,1],[54,1],[55,1],[117,1],[118,1],[119,1]],      # 2A_2+2A_1
       [[55,1],[56,1],[112,1],[113,1]],                     # A_3+A_1
       [[56,1],[57,1],[117,1],[118,1],[119,1]],             # 2A_2+A_1
       [[63,1],[106,1],[107,1]],                            # A_3
       [[56,1],[57,1],[117,1],[118,1]],                     # 2A_2
       [[60,1],[126,1],[127,1],[128,1]],                    # A_2+2A_1
       [[58,1],[59,1],[123,1],[124,1],[125,1]],             # A_2+3A_1
       [[63,1],[128,1],[129,1]],                            # A_2+A_1
       [[127,1],[130,1]],                                   # A_2
       [[63,1],[135,1],[136,1],[137,1]],                    # 4A_1
       [[141,1],[142,1],[143,1]],                           # 3A_1
       [[150,1],[151,1]],                                   # 2A_1
       [[157,1]]                                            # A_1
       ];

labels:=["E_8",
         "E_8(a_1)",
         "E_8(a_2)",
         "E_7+A_1",
         "E_7",
         "D_8",
         "E_7(a_1)+A_1",
         "E_7(a_1)",
         "D_8(a_1)",
         "D_7",
         "E_7(a_2)+A_1",
         "E_7(a_2)",
         "A_8",
         "E_6+A_1",
         "D_7(a_1)",
         "(D_7(a_1))_2",
         "E_6",
         "D_8(a_2)",
         "D_6+A_1",
         "A_7",
#         "(A_7)_3",
         "E_6(a_1)+A_1",
         "D_6",
         "D_7(a_2)",
         "E_6(a_1)",
         "D_5+A_2",
         "(D_5+A_2)_2",
         "A_6+A_1",
         "D_6(a_1)+A_1",
         "D_6(a_1)",
         "A_6",
#         "D_5+A_1",
         "2A_4",
         "A_5+A_2",
         "A_5+2A_1",
         "D_6(a_2)",
         "D_5",
         "D_5(a_1)+A_2",
         "(A_5+A_1)'",
         "(A_5+A_1)''",
         "D_4+A_2",
         "(D_4+A_2)_2",
         "A_4+A_3",
         "A_5",
         "D_5(a_1)+A_1",
         "A_4+A_2+A_1",

         "A_4+A_2",
         "D_5(a_1)",
         "D_4+A_1",
         "A_4+2A_1",
         "D_4",
         "A_4+A_1",
         "2A_3",
         "A_4",
         "D_4(a_1)+A_2",
         "A_3+A_2+A_1",
         "A_3+A_2",
         "(A_3+A_2)_2",
         "D_4(a_1)+A_1",
         "A_3+2A_1",
         "D_4(a_1)",
         "2A_2+2A_1",
         "A_3+A_1",
         "2A_2+A_1",
         "A_3",
         "2A_2",
         "A_2+2A_1",
         "A_2+3A_1",
         "A_2+A_1",
         "A_2",
         "4A_1",
         "3A_1",
         "2A_1",
         "A_1",
         ];

I:=[ [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ] ];#, [  ], [  ] ];

II:=[ [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ] ];#, [  ], [  ] ];

tori:=[ [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ], [  ] ];#, [  ], [  ] ];

#blocks:=[];
blocks:=[[[32, 7], [24, 1]], [[32, 5], [28, 1], [16, 3], [12, 1]], [[32, 3], [26, 1], [16, 7], [14, 1]], [[32, 1], [30, 1], [24, 2], [22, 2], [16, 3], [14, 1], [8, 4]], [[32, 1], [26, 3], [22, 1], [18, 3], [16, 1], [10, 3], [8, 1], [2, 3], [1, 2]], [[16, 14], [12, 2]], [[16, 11], [14, 3], [10, 2], [6, 1], [2, 1], [1, 2]], [[16, 11], [14, 3], [10, 2], [6, 1], [2, 1], [1, 2]], [[16, 10], [10, 6], [8, 1], [6, 1], [2, 6], [1, 2]], [[16, 7], [12, 9], [4, 6], [2, 2]], [[16, 10], [12, 2], [10, 2], [8, 3], [6, 1], [4, 2], [3, 2]], [[16, 10], [10, 6], [8, 1], [6, 1], [2, 6], [1, 2]], [[16, 6], [13, 2], [8, 14], [7, 2]], [[16, 10], [10, 6], [8, 1], [6, 1], [2, 6], [1, 2]], [[16, 3], [14, 5], [10, 2], [8, 10], [6, 4], [2, 2], [1, 2]], [[16, 3], [14, 1], [12, 6], [10, 2], [8, 7], [6, 1], [4, 8]], [[16, 10], [10, 6], [8, 1], [6, 1], [1, 14]], [[16, 2], [15, 2], [12, 4], [11, 4], [8, 6], [7, 2], [4, 8]], [[16, 3], [14, 5], [10, 2], [8, 10], [6, 4], [2, 2], [1, 2]], [[8, 4], [7, 6], [6, 8], [5, 8], [4, 10], [3, 6], [2, 10], [1, 8]], [[16, 2], [14, 2], [13, 2], [12, 2], [11, 2], [10, 2], [9, 2], [8, 4], [6, 2], [5, 2], [4, 4], [2, 4]], [[16, 1], [14, 5], [10, 10], [8, 1], [6, 5], [2, 10], [1, 4]], [[8, 28], [6, 4]], [[16, 2], [13, 6], [11, 2], [9, 6], [8, 2], [5, 6], [4, 2], [1, 8]], [[8, 27], [6, 1], [4, 5], [2, 3]], [[8, 27], [6, 1], [4, 4], [3, 2], [2, 2]], [[8, 24], [7, 2], [6, 4], [4, 2], [3, 2], [2, 2]], [[8, 27], [6, 1], [4, 5], [2, 1], [1, 4]], [[8, 27], [6, 1], [4, 5], [2, 1], [1, 4]], [[8, 22], [7, 6], [5, 4], [3, 2], [1, 4]], [[8, 20], [7, 4], [4, 12], [3, 4]], [[8, 20], [6, 6], [4, 8], [3, 2], [2, 6], [1, 2]], [[8, 20], [6, 4], [5, 4], [4, 6], [3, 2], [2, 6], [1, 2]], [[8, 14], [6, 18], [2, 12], [1, 4]], [[8, 27], [6, 1], [2, 6], [1, 14]], [[8, 13], [6, 3], [4, 30], [3, 2]], [[8, 14], [6, 18], [2, 12], [1, 4]], [[8, 20], [5, 12], [4, 2], [3, 2], [1, 14]], [[8, 11], [6, 7], [4, 26], [1, 14]], [[8, 11], [6, 7], [4, 26], [1, 14]], [[8, 12], [7, 2], [6, 2], [4, 30], [3, 2]], [[8, 14], [6, 18], [2, 6], [1, 16]], [[8, 11], [6, 7], [4, 26], [2, 6], [1, 2]], [[8, 10], [7, 2], [6, 4], [5, 4], [4, 24], [2, 6], [1, 2]], [[8, 6], [7, 10], [5, 4], [4, 20], [3, 8], [1, 6]], [[8, 11], [6, 7], [4, 26], [1, 14]], [[8, 1], [6, 27], [2, 36], [1, 6]], [[8, 6], [7, 2], [6, 12], [5, 4], [4, 14], [3, 2], [2, 16]], [[8, 1], [6, 27], [2, 26], [1, 26]], [[8, 4], [7, 6], [6, 8], [5, 8], [4, 10], [3, 6], [2, 10], [1, 8]], [[4, 60], [2, 4]], [[8, 2], [7, 10], [5, 20], [4, 2], [3, 10], [1, 24]], [[4, 56], [3, 8]], [[4, 54], [3, 2], [2, 12], [1, 2]], [[4, 54], [3, 2], [2, 10], [1, 6]], [[4, 50], [3, 10], [2, 6], [1, 6]], [[4, 54], [3, 2], [2, 10], [1, 6]], [[4, 46], [2, 30], [1, 4]], [[4, 54], [3, 2], [1, 26]], [[4, 44], [3, 4], [2, 28], [1, 4]], [[4, 46], [2, 24], [1, 16]], [[4, 40], [3, 12], [2, 18], [1, 16]], [[4, 46], [2, 10], [1, 44]], [[4, 28], [3, 36], [1, 28]], [[4, 22], [3, 14], [2, 52], [1, 14]], [[4, 26], [3, 6], [2, 62], [1, 2]], [[4, 14], [3, 30], [2, 34], [1, 34]], [[4, 2], [3, 54], [1, 78]], [[2, 120], [1, 8]], [[2, 110], [1, 28]], [[2, 92], [1, 64]], [[2, 58], [1, 132]]];

#ords:=[];
ords:=[ 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 16, 16, 8, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2 ];

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


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

Class(DRAMGlobals, rec(
    getOpts := meth(arg)
        local opts, brules, nrules, br, size, k, rb, lm, n, dram_datawidth, precision, dram_addrwidth,bb,throttle;

	rb := When (Length(arg) >= 2, arg[2], 2);
	#k := When (Length(arg) >= 2, arg[2], 2);
	lm := When (Length(arg) >= 3, arg[3], 8);
	n := When (Length(arg) >= 4, arg[4], 4);
	dram_datawidth := When (Length(arg) >= 5, arg[5], 256);
	precision := When (Length(arg) >= 6, arg[6], 64);
	dram_addrwidth := When (Length(arg) >= 7, arg[7], 27);
	bb := When (Length(arg) >= 8, arg[8], 1);
	throttle := When (Length(arg) >= 9, arg[9], 1);

	PrintLine("rb=",rb,", lm=",lm,", n=",n,", dram_datawidth=",dram_datawidth,", precision=",precision,", dram_addrwidth=",dram_addrwidth,", bb(BlackBoxMems)=",bb," throttle=",throttle);
	#PrintLine("k=",k,", lm=",lm,", n=",n,", dram_datawidth=",dram_datawidth,", precision=",precision,", dram_addrwidth=",dram_addrwidth,", bb(BlackBoxMems)=",bb);

	# Move this warning to breakdown rules
	# if(k*n > lm) then
	# 	PrintLine("Warning: Problem specification requires more than 2 stages!");
	# 	PrintLine("Warning: I can't handle it for now, sorry.");
	# 	lm := k*n;
	# 	PrintLine("Local memory size (lm) is assumed to be ",lm,"!");
	# 	PrintLine("..........");
	# fi;

    brules := When(IsRec(SpiralDefaults.breakdownRules),
        UserRecFields(SpiralDefaults.breakdownRules),
        Filtered(Dir(SpiralDefaults.breakdownRules), i->not i in SystemRecFields));
    nrules := rec();
    for br in brules do
        nrules.(br) := List(SpiralDefaults.breakdownRules.(br), i->CopyFields(i));
    od;
    opts := CopyFields(SpiralDefaults);
    opts.breakdownRules := nrules;

    #opts.breakdownRules.TCompose := [TCompose_tag];
    opts.breakdownRules.DFT := [DFT_Base, DFT_tSPL_CT_tiled, DFT_tSPL_push_tiled];
    #opts.breakdownRules.DFT := [];

	#opts.breakdownRules.MDDFT := [MDDFT_Base, MDDFT_tSPL_RowCol_break, MDDFT_tSPL_RowCol_push];
    opts.breakdownRules.MDDFT := [MDDFT_Base, MDDFT_tSPL_RowCol_break_2D, MDDFT_tSPL_RowCol_break_3D, MDDFT_tSPL_RowCol_push];

	opts.breakdownRules.TTwiddle := [ TTwiddle_dram ];

	#opts.breakdownRules.WHT := [WHT_tSPL_BinSplit, WHT_Base, WHT_BinSplit];

	# full applicable set of rules, should be used for all the algorithms
	opts.breakdownRules.TTensor := [AxI_IxB, IxB_AxI, L_BxI__L_AxI, AxI_L__BxI_L];
	#opts.breakdownRules.TTensor := [AxI_L__BxI_L];
	# restricted to only AxI_L__BxI_L since verification scripts assume that the
	# generated hardware is based this breakdown

	#opts.breakdownRules.TTensorI := Concat([IxA_scratch_push, IxA_base, AxI_base, IxA_L_base, L_IxA_base], [IxA_scratch, AxI_scratch, IxAL_scratch]);

	opts.breakdownRules.TTensorI := [IxA_tile_base, AxI_tile_base, L_IxA_tile_base, IxA_L_tile_base, IxA_tile, AxI_tile, L_IxA_tile, IxA_L_tile];
	opts.breakdownRules.TL := [L_base, I_tileRd, I_tileWr, L_tileRd, L_tileWr, I_cubeRd, I_cubeWr, Ln3n2_cubeRd, Ln3n_cubeWr,
								InLn2n_cubeRd, InLn2n_cubeWr];
	#opts.breakdownRules.TCompose := [AB_tile];

	opts.throttle := throttle;

	opts.tags := [ADram(rb,lm)];
	#opts.tags := [ATile(k,lm)];

#	opts.formulaStrategies.sigmaSpl := [ MergedRuleSet(RulesSumsScratch, RulesFuncSimpScratch, RulesDiag, RulesDiagStandalone, RulesStrengthReduce, RulesRCScratch,RulesII,OLRules) ];
#    opts.formulaStrategies.preRC := [ MergedRuleSet(RulesSumsScratch, RulesFuncSimpScratch, RulesDiag, RulesDiagStandalone, RulesStrengthReduce, RulesRCScratch, RulesII, OLRules), (s,o) -> ScratchModel.updateInfo(s) ];
#	opts.formulaStrategies.rc := [ MergedRuleSet(RulesSums, RulesFuncSimp, RulesDiag, RulesDiagStandalone, RulesStrengthReduce, RulesRCScratch, RulesII, OLRules) ];
    #opts.formulaStrategies.postProcess := [(s, opts) -> compiler.BlockSums(opts.globalUnrolling, s)];
    opts.size := [n,n];
	#opts.swp := swp;
    #opts.globalUnrolling := globalUnrolling;

	opts.dram_datawidth := dram_datawidth;
	opts.precision := precision;
	opts.dram_addrwidth := dram_addrwidth;
	opts.bb := bb;

#    opts.sumsgen := ScratchSumsGen;
#	opts.codegen := ScratchCodegen;
#    opts.unparser := CScratchUnparserProg;

    opts.memModifier := "__memory";
    opts.scratchModifier := "__scratch";
    opts.arrayDataModifier := "__rom";
    opts.romModifier := "__rom";

	opts.includes := [];
    Add(opts.includes, "\"scratch.h\"");

    opts.dmaSignal := (self, opts) >> "DMA_signal";
    opts.dmaWait := (self, opts) >> "DMA_wait";
    opts.cpuSignal := (self, opts) >> "CPU_signal";
    opts.cpuWait := (self, opts) >> "CPU_wait";
    opts.dmaFence := (self, opts) >> "DMA_fence";
    opts.dmaLoad := (self, opts) >> "DMA_load";
    opts.dmaStore := (self, opts) >> "DMA_store";
#    opts.model := ScratchModel;

    return opts;
    end
));


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

Declare(PropagateUnification);

list_dup := (n, a) -> Cond(IsList(a), Checked(Length(a)=n, a), Replicate(n, a));

getXTypeOpts := (t, opts) -> When(IsBound(t.a.t_in),  List(t.a.t_in,  e -> TPtr(e)), list_dup(t.arity()[2], opts.XType));
getYTypeOpts := (t, opts) -> When(IsBound(t.a.t_out), List(t.a.t_out, e -> TPtr(e)), list_dup(t.arity()[1], opts.YType));

# TypeOpts(<sums>, <opts>) returns options with 'XType' and 'YType' fields set acording
# to 't_in' and 't_out' attributes (when available).
# Playing ping-pong between SumsUnification and TypeOpts allows to perform search for OL and
# multi data type formulas on existing DP search infrastructure.

TypeOpts := (sums, opts) -> Cond( not IsBound(opts.XType) or not IsBound(opts.YType), opts,
    CopyFields(opts, rec(
        XType := StripList(getXTypeOpts(sums, opts)),
        YType := StripList(getYTypeOpts(sums, opts))
)));

# SumsUnification sets data type 't_in' and 't_out' attributes on sums tree..
# It propagates 't_in' types of sums root object (if available) or opts.XType.
SumsUnification := function(sums, opts)
    if IsBound(opts.doSumsUnification) and opts.doSumsUnification then
        sums := PropagateUnification(sums, List(getXTypeOpts(sums, opts), e -> e.t));
    fi;
    return sums;
end;

Class(PropagateUnification, HierarchicalVisitor, rec(

     # makes sure the object is not a polymorphic function/SPL like L,
     # which would cause trouble because they are handled below via
     # FuncClass, and thus .a.t_in and t_out are not set
     _isSPL  := x -> IsSPL(x) and not IsFunction(x),

     _unify  := (t1, t2) -> Cond( IsList(t1) and IsList(t2) and Length(t1)=Length(t2),
                                   List([1..Length(t1)], i -> UnifyPair(t1[i], t2[i])),
                               IsList(t1) and IsType(t2),
                                   List([1..Length(t1)], i -> UnifyPair(t1[i], t2)),
                               IsType(t1) and IsType(t2),
                                   UnifyPair(t1, t2),
                               Error("PropagateUnification: type dimensions mismatch")),
     _to_cplx := (lst) -> List(lst, e -> T_Complex(e)),
     _to_real := (lst) -> List(lst, e -> e.realType()),

     TCast := meth(self, o, inputs)
         o.params[3] := UnifyTypes(inputs :: [o.params[3]]);
         return o.withA( t_in  => [o.params[3]],
                         t_out => [o.params[2]]);
     end,

     TCvt := (self, o, inputs) >>
         o.withA( t_in  => [o.isa_from().t.base_t()],
                  t_out => [o.isa_to().t.base_t()  ] ),

     Cvt := ~.TCvt,

     Compose := (self, o, inputs) >> let(
         ch    := DropLast(ScanR( o.rChildren(), (l, e) -> When(IsList(l), self(e, l), self(e, l.a.t_out)), inputs ), 1),
         o.from_rChildren(ch).withA( t_in  => Last(ch).a.t_in,
                                     t_out => ch[   1].a.t_out)),

     RC := ( self, o, inputs) >> let(
         ch := List(o.rChildren(), e -> When( self._isSPL(e), self(e, self._to_cplx(inputs)), e)),
         o.from_rChildren(ch).withA(t_in => self._to_real(ch[1].a.t_in), t_out => self._to_real(ch[1].a.t_out))),
     GRC := ~.RC,
     TRC := ~.RC,

     BaseMat := (self, o, inputs) >> let(
         dmn := List(o.dmn(), e -> e.t.base_t()),
         rng := List(o.rng(), e -> e.t.base_t()),
         inp := List(Zip2(dmn, inputs), UnifyTypes),
         # we have no idea how to propagate data types for objects which arity is other than [1, 1],
         # let's hope object defined it's rng() and dmn() data types or uses same data type everywhere
         # or data type propagated horizontally in the case of same arity on the left and on the right.
         rnginp := Replicate( Length(rng), UnifyTypes(inp) ),
         out := List(Zip2(rng, Cond( Length(dmn)=Length(rng), inp, rnginp )), UnifyTypes),
         o.withA( t_in => inp, t_out => out )),

     ClassSPL := (self, o, inputs) >> let(
         ch := List(o.rChildren(), e -> When( self._isSPL(e), self(e, inputs), e )),
         spl_ch := Filtered(ch, self._isSPL),
         When( spl_ch <> [],
             o.from_rChildren(ch).withA(
                 t_in  => FoldL1(List(spl_ch, x->x.a.t_in ), self._unify),
                 t_out => FoldL1(List(spl_ch, x->x.a.t_out), self._unify)),
             self.BaseMat(o, inputs))),

     Cross := meth (self, o, inputs)
         local ch, i, j, d;
         ch := o.rChildren();
         j  := 1;
         for i in [1..Length(ch)] do
             d     := Length(ch[i].dmn());
             ch[i] := self(ch[i], Flat(inputs{[j..j+d-1]}));
             j     := j + d;
         od;
         return o.from_rChildren(ch).withA(
                     t_in  => Flat(List(ch, e -> e.a.t_in)),
                     t_out => Flat(List(ch, e -> e.a.t_out)));
     end,

     SMPBarrier := (self, o, inputs) >> let( ch := self(o.child(1), inputs),
         ObjId(o)(o.nthreads, o.tid, ch).withA( t_in => ch.a.t_in, t_out => ch.a.t_out)),

     VContainer := ~.ClassSPL,
     SumsBase   := ~.ClassSPL,
     VPerm      := ~.BaseMat,

     TaggedNonTerminal := meth( self, o, inputs)
         local tag, tL, tR, tM;
         tag := FirstDef(o.getTags(), t -> Global.autolib.IsACVec(t), false);
         if tag=false then
             return self.ClassSPL(o, inputs);
         else
             tL := List(Flat([tag.isaL()]), e->e.t.base_t());
             tR := List(Flat([tag.isaR()]), e->e.t.base_t());
             tM := List(list_dup(o.arity()[2], tag.isaM()), e->e.t.base_t());
             if IsComplexT(inputs[1]) then
                 tL := List(tL, e -> T_Complex(e));
                 tR := List(tR, e -> T_Complex(e));
                 tM := List(tM, e -> T_Complex(e));
             fi;
             return self.ClassSPL(o, tM).withA(t_in => tR, t_out => tL);
         fi;
     end,

     GTBase := ~.TaggedNonTerminal,

     FuncClass     := Ignore,
     FuncClassOper := Ignore,
));


#############################################################################
####
##
#W  anupqxdesc.gi              ANUPQ package                    Werner Nickel
#W                                                                Greg Gamble
##
##  Installs functions to do recursive development of a descendants tree.
##  If ANUPQ is loaded from XGAP the development is seen graphically.
##
#Y  Copyright (C) 2001  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
##

#############################################################################
##
#F  PqDescendantsTreeCoclassOne([<i>]) . . . generate a coclass one des. tree
##
##  for the <i>th  or  default  interactive  {\ANUPQ}  process,  generates  a
##  descendant tree for the  group  of  the  process  (which  must  be  a  pc
##  $p$-group) consisting of descendants of $p$-coclass 1  and  extending  to
##  the class determined by the option `TreeDepth' (or 6  if  the  option  is
##  omitted). In an  {\XGAP}  session,  a  graphical  representation  of  the
##  descendants tree appears  in  a  separate  window.  Subsequent  calls  to
##  `PqDescendantsTreeCoclassOne' for the same process may be used to  extend
##  the descendant tree from the last descendant  computed  that  itself  has
##  more than one descendant. `PqDescendantsTreeCoclassOne' also accepts  the
##  options  `CapableDescendants'  (or  `AllDescendants')  and  any   options
##  accepted     by     the     interactive     `PqDescendants'      function
##  (see~"PqDescendants!interactive").
##
##  *Notes*
##
##  `PqDescendantsTreeCoclassOne'    first    calls    `PqDescendants'.    If
##  `PqDescendants' has already been called for  the  process,  the  previous
##  value computed is used and a warning is `Info'-ed at `InfoANUPQ" level 1.
##
##  As each descendant is processed its unique  label  defined  by  the  `pq'
##  program and number of descendants is `Info'-ed at `InfoANUPQ' level 1.
##
##  `PqDescendantsTreeCoclassOne' is an  ``experimental''  function  that  is
##  included to demonstrate the sort of things that  are  possible  with  the
##  $p$-group generation machinery.
##
InstallGlobalFunction( PqDescendantsTreeCoclassOne, function( arg )
    local   datarec,  title,  des,  node;

    datarec := ANUPQ_ARG_CHK("PqDescendantsTreeCoclassOne", arg);
    if datarec.procId = 0 then
        Error("non-interactive `PqDescendantsTreeCoclassOne' is not ",
              "currently supported\n");
    fi;
    if IsBound(datarec.treepos) then
        PQX_RECURSE_DESCENDANTS( datarec,
                                 datarec.treepos.class,
                                 datarec.treepos.node,
                                 datarec.treepos.ndes );
        return;
    fi;

    des := PqDescendants( datarec.procId : StepSize := 1 );
    if IsPackageMarkedForLoading("xgap","") then
        title := Concatenation( "Descendants Tree, p=",
                     String( PrimePGroup(datarec.group) ),
                     ", order: ",
                     String( Size(datarec.group) ),
                     ", class <= ",
                     String( VALUE_PQ_OPTION("TreeDepth", 6, datarec) ) );
        if VALUE_PQ_OPTION("CapableDescendants",
                           not VALUE_PQ_OPTION("AllDescendants", true, datarec),
                           datarec) then
            Append( title, ", capable descendants" );
        else
            Append( title, ", all descendants" );
        fi;
        datarec.xgapsheet := GraphicSheet( title, 800, 700 );
        datarec.nextX     := [0, 0];
        node := PQX_PLACE_NEXT_NODE( datarec, 1 );
    else
        node := 0;
    fi;

    PQX_RECURSE_DESCENDANTS( datarec, 2, node, Length(des) );

    if datarec.calltype = "non-interactive" then
        PQ_COMPLETE_NONINTERACTIVE_FUNC_CALL(datarec);
        if IsBound( datarec.setupfile ) then
            return true;
        fi;
    fi;
end);

#############################################################################
##
#F  PQX_PLACE_NEXT_NODE( <datarec>, <class> ) . place a node on an XGAP sheet
##
##  places a node for the current descendant of class <class> on the  {\XGAP}
##  sheet `<datarec>.xgapsheet'.
##
InstallGlobalFunction( PQX_PLACE_NEXT_NODE, function( datarec, class )
    local   y,  x;

    y := 40 * class;
    datarec.nextX[class] := datarec.nextX[class] + 16;
    x := datarec.nextX[class];

    Disc( datarec.xgapsheet, x, y, 6 );
    return [x,y];
end);

#############################################################################
##
#F  PQX_MAKE_CONNECTION( <datarec>, <a>, <b> ) . .  join two XGAP sheet nodes
##
##  joins the nodes <a> and <b> on the  {\XGAP}  sheet  `<datarec>.xgapsheet'
##  with a straight line, where <a> and <b> are each `[<x>, <y>]'  coordinate
##  pairs (lists) of integers.
##
InstallGlobalFunction( PQX_MAKE_CONNECTION, function( datarec, a, b )

    Line( datarec.xgapsheet, a[1], a[2], b[1]-a[1], b[2]-a[2] );
end);

#############################################################################
##
#F  PQX_RECURSE_DESCENDANTS(<datarec>,<class>,<parent>,<n>)  extend des. tree
##
##  extends a descendant tree of coclass 1 descendants from the current  `pq'
##  descendant  that  has  <n>  descendants  from  class  <class>  to   class
##  determined by the option `TreeDepth' (or 6, by  default)  from  the  node
##  <parent> which is an `[<x>, <y>]' coordinate pair (list) of integers.
##
InstallGlobalFunction( PQX_RECURSE_DESCENDANTS,
function( datarec, class, parent, n)
local   i,  node,  nr;

    if class > VALUE_PQ_OPTION("TreeDepth", 6, datarec) then
        datarec.treepos := rec(class:=class, node:=parent, ndes:=n);
        return;
    fi;

    if parent <> 0 then
        datarec.nextX[class] := datarec.nextX[class] + 14;
    fi;
    for i in [1..n] do
        PQ_PG_RESTORE_GROUP( datarec, class, i );
        PQ_PG_EXTEND_AUTOMORPHISMS( datarec );
        nr := PqPGConstructDescendants( datarec.procId : StepSize := 1 );

        if parent <> 0 and (not datarec.CapableDescendants or nr > 0) then
            # Place a node on the Graphic Sheet and connect it with its
            # parent node.
            node := PQX_PLACE_NEXT_NODE( datarec, class );
            PQX_MAKE_CONNECTION( datarec, node, parent );
        else
            node := 0;
        fi;

        Info(InfoANUPQ, 1, "Number of descendants of group ", datarec.gpnum,
                           " to class ", class, ": ", nr);
        if nr > 0 then
            if IsBound(datarec.nextX) and
               not IsBound(datarec.nextX[class + 1]) then
                datarec.nextX[class + 1] := 0;
            fi;
            PQX_RECURSE_DESCENDANTS( datarec, class+1, node, nr );
        fi;
    od;
    return;
end);

#E  anupqxdesc.gi . . . . . . . . . . . . . . . . . . . . . . . . . ends here


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

_neon_binop_ev := rec(
    ev := self >> self.t.value(self.semantic(self._vval(1), self._vval(2), [])).ev(),
);

############### HALF NEON #############
Class(vunpacklo_half, _neon_binop_ev, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 2, 1)));

Class(vunpackhi_half, _neon_binop_ev, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 2, 1)));

Class(vrev_half, VecExp_2.unary(), rec(
    semantic := (in1, p) -> vrev64(in1,2),
));

Class(vtrnq_half, _neon_binop_ev, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> [vtransposelo(in1, in2, 2), vtransposehi(in1, in2, 2)],
    computeType := self >> TVect(TVect(T_Real(32), 2), 2),
));

Class(vextract_half, VecExp_2.unary(), rec(
    computeType := self >> TVect(T_Real(32), 2),
    semantic := (in1, p) -> Checked(Length(in1)=2, in1[p[1]+1]),
    ev := self >> self.t.value(self.semantic(self._vval(1), self.args[2].p)).ev(),
));

Class(vmulcx_half, VecExp_2.binary());
Class(vswapcx_half, VecExp_2.unary());

Class(vload1_half,  VecExp_2.binary());
Class(vstore1_half, VecExpCommand);

# vdup_lane_half(<float32x2>, <int lane_num>) ==> float32x2
Class(vdup_lane_half, VecExp_2.binary(), rec(
    ev := self >> let(lane := self.args[2].ev(),
	Checked(IsInt(lane), lane >=0, lane <= 1,
	        self.t.value( Replicate(2, self.args[1].ev()[1+lane]) )))
));

# vext_half(<float32x2>, <float32x2>, 1) ==> float32x2
# vext_half([a, b], [c, d], 1) = [b c]
Class(vext_half, VecExp_2.ternary(), rec(
    ev := self >> let(num := self.args[3].ev(),
	Checked(IsInt(num), num=1,
	        self.t.value( self.args[1].ev()[2], self.args[2].ev()[1])))
));

#############################################################
# Composite instructions. Serve as a bridge between NEON and
# TL ISA DB. Later rewritten into combination of vuzpq_32f,
# vzipq_32f and vtrnq_32f with vextract_neon_4x32f.
#

Class(vpacklo_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [1, 3, 1, 3], 4, 1)));

Class(vpackhi_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [2, 4, 2, 4], 4, 1)));

Class(vunpacklo_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 4, 1)));

Class(vunpackhi_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 4, 1)));

Class(vtransposelo_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> vtransposelo(in1, in2, 4)));

Class(vtransposehi_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> vtransposehi(in1, in2, 4)));

Class(vunpacklolo2_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 4, 2)));

Class(vunpacklohi2_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [1,2,3,4], 4, 1)));

Class(vunpackhilo2_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [3,4,1,2], 4, 1)));

Class(vunpackhihi2_neon, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 4, 2)));

Class(vrev_neon, VecExp_4.unary(), rec(
		semantic := (in1, p) -> vrev64(in1,4),
));
#############################################################

Class(vuzpq_32f, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> [shuffle(in1, in2, [1, 3, 1, 3], 4, 1), shuffle(in1, in2, [2, 4, 2, 4], 4, 1)],
    computeType := self >> TVect(TVect(T_Real(32), 4), 2),
));

Class(vzipq_32f, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> [unpacklo(in1, in2, 4, 1), unpackhi(in1, in2, 4, 1)],
    computeType := self >> TVect(TVect(T_Real(32), 4), 2),
));

Class(vtrnq_32f, _neon_binop_ev, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> [vtransposelo(in1, in2, 4), vtransposehi(in1, in2, 4)],
    computeType := self >> TVect(TVect(T_Real(32), 4), 2),
));

Class(vextract_neon_4x32f, VecExp_4.unary(), rec(
    computeType := self >> TVect(T_Real(32), 4),
    semantic := (in1, p) -> Checked(Length(in1)=2, in1[p[1]+1]),
    ev := self >> self.t.value(self.semantic(self._vval(1), self.args[2].p)).ev(),
));

Class(vmulcx_neon, VecExp_4.binary());
Class(vswapcx_neon, VecExp_4.unary());

# vload1_neon(<float*>, <float32x4>, <int lane_num>) => float32x4
Class(vload1_neon,  VecExp_4.binary());

# vload_half_neon(<float*>) => float32x2
Class(vload_half_neon, VecExp_2.unary());

# vstore1_neon(<float*>, <float32x4>, <int lane_num>)
Class(vstore1_neon, VecStoreCommand.binary());

# vstore2lo_neon(<float*>, <float32x4>)
Class(vstore2lo_neon, VecStoreCommand.binary());

# vstore2hi_neon(<float*>, <float32x4>)
Class(vstore2hi_neon, VecStoreCommand.binary());

# vcombine_neon(<float32x2>, <float32x>) => float32x4
Class(vcombine_neon, VecExp_4.binary());

# vdup_lane_neon(<float32x4>, <int lane_num>) ==> float32x4
Class(vdup_lane_neon, VecExp_4.binary(), rec(
    ev := self >> let(lane := self.args[2].ev(),
	Checked(IsInt(lane), lane >=0, lane <= 3,
	        self.t.value( Replicate(4, self.args[1].ev()[1+lane]) )))
));

# vext_neon(<float32x4>, <float32x4>, 1) ==> float32x4
# vext_neon([a, b, c, d], [e, f, g, h], 1) = [d e f g]
# vext_neon([a, b, c, d], [e, f, g, h], 2) = [c d e f]
Class(vext_neon, VecExp_4.ternary(), rec(
    ev := self >> let(num := self.args[3].ev(),
	Checked(IsInt(num), num>=0, num<=3,
	        self.t.value( self.args[1].ev(){[5-num .. 4]} :: self.args[2].ev(){[1..4-num]})))
));


Class(RulesMPIPromoteNT, RuleSet);

RewriteRules(RulesMPIPromoteNT, rec(
    DAG_remove_mpi := Rule([@(1,TDAG), [@(2, TDAGNode), @(3),
            @(4).cond(e->IsList(e) and Length(e) = 1 and ForAll(Collect(e[1], var), v-> v.id in ["Y", "Yptr"])),
            @(5).cond(e->IsList(e) and Length(e) = 1 and ForAll(Collect(e[1], var), v-> v.id in ["X", "Xptr"])),
            ...],...],
        e-> @(2).val.params[1].withTags([AParMPI(rec(Y := @(4).val, X := @(5).val))])),

    TFunc_mergeTags := Rule([@(1, TFCall, e->e.hasTags()), @(2).cond(e->e.hasTags() and let(tg := e.getTags()[1], IsBound(tg.isParMPI) and tg.isParMPI)), ...],
        e-> ApplyFunc(TFCall, [@(2).val.setTags([])]::Drop(@(1).val.params,1)).withTags(
            [ApplyFunc(ObjId(@(1).val.getTags()[1]), @(1).val.getTags()[1].params::@(2).val.getTags()[1].params)]::Drop(@(1).val.getTags(),1))
    )
));

RewriteRules(RulesRC, rec(
    RC_MPISum := Rule([RC, @(1, MPISum)],
        e -> let(s:=@(1).val, MPISum(s.nthreads, s.tid, s.var, s.domain, RC(s.child(1))))),

    RC_MPIPrm := Rule([RC, @(1,MPIPrm)],
        e -> MPIRCPrm(@(1).val.func)),

    RC_MPIGath := Rule([RC, @(1, MPIGath)], e -> MPIGath(fTensor(@(1).val.func, fId(2)))),

    RC_MPIScat := Rule([RC, @(1, MPIScat)], e -> MPIScat(fTensor(@(1).val.func, fId(2))))

));

Class(LocalizeMPIRules, RuleSet);
RewriteRules(LocalizeMPIRules, rec(
    merge_GPS := ARule(Compose, [@(1, MPIGath), @(2, MPIRCPrm), @(3, MPIScat)],
        e->let(p := @(1).val.free()[1].range, f := @(2).val.func.params,
               [ MPIRCPrm(ApplyFunc(ObjId(@(2).val.func), [f[1]/p]::Drop(f, 1)))])),

    merge_GRCDS := ARule(Compose, [@(1, MPIGath), @(2, RCDiag), @(3, MPIScat)],
        e->let(p := @(1).val.free()[1].range, rcdf := @(2).val.element,
               [ RCDiag(FDataOfs(rcdf.var, rcdf.len/p, rcdf.ofs)) ])),

    drop_NoPull := Rule(@(1, NoPull), e->e.child(1)),

    drop_MPISum := Rule(@(1, MPISum), e->e.child(1)),

    drop_GathScat := ARule(Compose, [@(1, MPIGath), @(2, MPIScat)],
        e-> [  ])
));

Class(LocalizeMPIRulesCleanup, RuleSet);
RewriteRules(LocalizeMPIRulesCleanup, rec(
    drop_MPIGathScath := ARule(Compose, [@(1, [MPIGath, MPIScat])], e-> [])
));

FullLocalizeMPI := [MergedRuleSet(RulesSums, LocalizeMPIRules), LocalizeMPIRulesCleanup];


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

Class(SVECodegen, DefaultCodegen, rec(
    GathH :=  ( self, o, y, x, opts ) >> assign(nth(y, 0), sve_gath(nth(x, o.base).toPtr(TVectSVE), o.n, opts.pg, o.stride)),
#    ScatH :=  ( self, o, y, x, opts ) >> sve_scat(nth(y, o.base).toPtr(TVectSVE), o.n, opts.pg, o.stride, nth(x, 0)),
    ScatH :=  ( self, o, y, x, opts ) >> sve_scat(nth(y, o.base).toPtr(TReal), o.n, opts.pg, o.stride, nth(x, 0)),

    VGath_L :=  ( self, o, y, x, opts ) >> let(_v := var.fresh_t("svex2_", TVectSVEx2),
        decl([_v], chain(
            assign(_v, sve_ld2(nth(x, o.base).toPtr(TReal), o.n, opts.pg)),
            assign(nth(y, 0), velem(_v, 0)),
            assign(nth(y, 1), velem(_v, 1))
        ))
    ),

    VScat_L :=  ( self, o, y, x, opts ) >> let(_v := var.fresh_t("svex2_", TVectSVEx2),
        decl([_v], chain(
            assign(velem(_v, 0), nth(x, 0)),
            assign(velem(_v, 1), nth(x, 1)),
            sve_st2(nth(y, o.base).toPtr(TReal), o.n, opts.pg, _v)
        ))
    )

));


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

# Permutation from standard to gap
# / 1 2 3 4 \
# \ 2 4 3 1 /
# Position(pr,Permuted([2,3,4,2],(2,4,1)))=24;

#......Ross Jordan Block Sizes........Donna & Ross nilpotents........(in GAP notation)
reps:=[[[2,1],[4,1],[3,1],[1,1]],    # F_4
       [[2,1],[4,1],[7,1],[5,1]],    # F_4(a_1) [[2,1],[4,1],[10,1],[1,1]],
       #[[6,1],[10,1],[1,1],[5,1]],   # F_4(a_2) [[9,1],[1,1],[10,1],[4,1]],
       [[1,1],[5,1],[6,1],[10,1]],   # F_4(a_2) [[9,1],[1,1],[10,1],[4,1]],    I want it ordered
       [[4,1],[3,1],[1,1]],          # C_3      [[1,1],[3,1],[4,1]],
       [[2,1],[4,1],[3,1]],          # B_3      the same
       [[4,1],[6,1],[10,1],[18,1]],  # F_4(a_3) [[4,1],[13,1],[11,1],[12,1]],
       [[4,1],[1,1],[10,1],[20,1]],  # C_3(a_1)^{(2)}
       [[4,1],[1,1],[10,1]],         # C_3(a_1) [[1,1],[10,1],[4,1]],
       [[2,1],[3,1],[1,1],[15,1]],   # sA_2A_1^{(2)}
       [[2,1],[3,1],[1,1]],          # sA_2A_1  [[3,1],[1,1],[2,1]],
       [[4,1],[3,1],[13,1]],         # B_2^{(2)}
       [[4,1],[3,1]],                # B_2      the same
       [[2,1],[4,1],[1,1]],          # A_2sA_1  the same
       [[3,1],[1,1]],                # sA_2     the same
       [[2,1],[4,1]],                # A_2      the same
#       [[2,1],[3,1]],                # A_1sA_1  [[2,1],[1,1]],
#       [[3,1],[10,1]],               # sA_1^{(2)}
#       [[3,1]],                      # sA_1     [[1,1]],
#       [[2,1]]                       # A_1      [[2,1]]
       [[2,1],[1,1]],                # A_1sA_1  [[2,1],[1,1]],
#       [[1,1],[15,1]],               # sA_1^{(2)}  above*reflection[1]*reflection[3]
       [[21,1],[24,1]],              # sA_1^{(2)} Liebeck & Seitz ---> is already in the right position
       [[1,1]],                      # sA_1     [[1,1]],
       [[2,1]]                       # A_1      [[2,1]]
       ];

I:=[[],     # F_4
    [3],    # F_4(a_1)
    [3,4],  # F_4(a_2)
    [],     # C_3
    [],     # B_3
    [2,3,1],# F_4(a_3)
    [3],    # C_3(a_1)^{(2)}
    [3],    # C_3(a_1)
    [],     # sA_2A_1^{(2)}
    [],     # sA_2A_1
    [],     # B_2^{(2)}
    [],     # B_2
    [],     # A_2sA_1
    [],     # sA_2
    [],     # A_2
    [],     # A_1sA_1
    [],     # sA_1^{(2)}
    [],     # sA_1
    []      # A_1
    ];

#
# The parabolic where the representative is distinguished
#
II:=[[1,2,3,4],   # F_4
     [1,2,3,4],   # F_4(a_1)
     [1,2,3,4],   # F_4(a_2)
     [4,3,1],     # C_3
     [2,4,3],     # B_3
     [1,2,3,4],   # F_4(a_3)
     [1,2,3,4],   # C_3(a_1)^{(2)}
     [4,3,1],     # C_3(a_1)
     [1,2,3,4],   # sA_2A_1^{(2)}
     [3,1,2],     # sA_2A_1
     [1,2,3,4],   # B_2^{(2)}
     [4,3],       # B_2
     [2,4,1],     # A_2sA_1
     [3,1],       # sA_2
     [2,4],       # A_2
     [2,1],       # A_1sA_1
     [1,2,3,4],   # sA_1^{(2)}
     [1],         # sA_1
     [2]          # A_1
     ];

labels:=["F_4",
         "F_4(a_1)",
         "F_4(a_2)",
         "C_3",
         "B_3",
         "F_4(a_3)",
         "C_3(a_1)^{(2)}",
         "C_3(a_1)",
         "(\\tilde A_2A_1)^{(2)}",
         "\\tilde A_2A_1",
         "B_2^{(2)}",
         "B_2",
         "A_2\\tilde A_1",
         "\\tilde A_2",
         "A_2",
         "A_1\\tilde A_1",
         "\\tilde A_1^{(2)}",
         "\\tilde A_1",
         "A_1"
         ];

tori:=[
       [2,2,2,2],   # F_4
       [2,2,0,2],   # F_4(a_1)
       [2,0,0,2],   # F_4(a_2)
       [2,-9,2,2],  # C_3
       [-6,2,2,2],  # B_3
       [0,0,0,2],   # F_4(a_3)
       [2,-5,0,2],  # C_3(a_1)^{(2)}
       [2,-5,0,2],  # C_3(a_1)
       [2,2,2,-5],  # (sA_2A_1)^{(2)}
       [2,2,2,-5],  # sA_2A_1
       [-3,-4,2,2], # (B_2)^{(2)}
       [-3,-4,2,2], # B_2
       [2,2,-3,2],  # A_2sA_1
       [2,0,2,-4],  # sA_2
       [0,2,-2,2],  # A_2
       [2,2,-1,-1], # A_1sA_1
       #[2,0,-1,0],  # (sA_1)^{(2)}
       [2,2,2,2],   # (sA_1)^{(2)} ---> is already in the right position
       [2,0,-1,0],  # sA_1
       [0,2,0,-1]   # A_1
    ];

#blocks:=[];
blocks:=[[[16, 2], [13, 1], [7, 1]],             # F_4
         [[8, 6], [4, 1]],                       # F_4(a_1)
         [[8, 4], [7, 1], [6, 1], [4, 1],[3, 1]],# F_4(a_2)
         [[8, 4], [4, 3], [2, 3], [1, 2]],       # C_3
         [[8, 1], [6, 6], [2, 3], [1, 2]],       # B_3
         [[4, 12], [2, 2]],                      # F_4(a_3)
         [[4, 10], [3, 1], [2, 4], [1, 1]],      # C_3(a_1)^{(2)}
         [[4, 10], [3, 1], [2, 3], [1, 3]],      # C_3(a_1)
         [[4, 9], [3, 3], [2, 3], [1, 1]],       # sA_2A_1^{(2)}
         [[4, 8], [2, 10]],                      # sA_2A_1
         [[4, 10], [3, 1], [2, 3], [1, 3]],      # B_2^{(2)}
         [[4, 10], [3, 1], [1, 9]],              # B_2
         [[4, 6], [3, 4], [2, 8]],               # A_2sA_1
         [[4, 8], [2, 6], [1, 8]],               # sA_2
         [[4, 2], [3, 12], [1, 8]],              # A_2
         [[2, 24], [1, 4]],                      # A_1sA_1
         [[2, 21], [1, 10]],                     # sA_1^{(2)}
         [[2, 16], [1, 20]],                     # sA_1
         [[2, 16], [1, 20]]                      # A_1
         ];

#ords:=[];
ords:=[ 16, 8, 8, 8, 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2 ];

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


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

Declare(GathMem);
Declare(ScatMem);

Declare(MultiBufISum);
Declare(MultiBufISumFinal);
Declare(MemISum);
Declare(R2Sum);

Declare(MultiBufDistSum);

Class(FDataOfs_mbuf, FDataOfs);
Class(RemoteData, Data, rec(
    new := (self, var, altbuf, value, ofs, spl) >> Checked(IsVar(var), IsSPL(spl), SPL(WithBases(self, rec(
           var:=var,
           altbuf:=altbuf,
           value:=value,
           ofs := ofs,
           _children:=[spl],
           dimensions := spl.dimensions)))),
    #print := (self) >> Print(DarkRedStr(self.name), "(", self.var, ", ", self.len, ", ", self.ofs, ")")

));

# Used to mark BBs which have RemoteDataUnrolled
Class(RemoteDataInit, BB, rec(
    doNotMarkBB := true
));

Class(RemoteDataUnrolled, RemoteData);
Class(RemoteDataNoBody, RemoteData);

#F GathMem(func, pkSize)
#F Explicit Gather from memory (like DMA)
Class(GathMem, BaseMat, SumsBase, rec(
    sums := self >> self,
    isReal := self >> true,
    dims := self >> self.dimensions,
    needInterleavedLeft := self >> false,
    needInterleavedRight := self >> false,
    cannotChangeDataFormat := self >> true,
    totallyCannotChangeDataFormat := self >> true,
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func, self.pkSize],
    rSetChild := rSetChildFields("func", "pkSize"),
    #-----------------------------------------------------------------------
    new := (self, func, pkSize) >> SPL(WithBases(self,
        rec(func       := FF(func),
            pkSize     := pkSize,
            dimensions := [pkSize*func.domain(), pkSize*func.range()]
            ))),
    #-----------------------------------------------------------------------
    area := self >> self.pkSize * self.func.domain(),

    transpose := self >> ScatMem(self.func, self.pkSize),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.func, ", ", self.pkSize, ")"),
    #-----------------------------------------------------------------------
    toAMat := self >>
          let(pkSize   := self.pkSize,
              n        := pkSize*self.func.domain(),
              N        := pkSize*self.func.range(),
              func     := fTensor(self.func, fId(pkSize)).lambda(),
              AMatMat(List([0..n-1], row -> BasisVec(N, func.at(row).ev())))
          )
));

#F ScatMem(func, pkSize)
#F Explicit Scatter to memory (like DMA)
Class(ScatMem, BaseMat, rec(
    sums := self >> self,
    dims := self >> self.dimensions,
    isReal := self >> true,
    needInterleavedLeft := self >> false,
    needInterleavedRight := self >> false,
    cannotChangeDataFormat := self >> true,
    totallyCannotChangeDataFormat := self >> true,
    area := self >> self.transpose().area(),
    toAMat := self >> TransposedAMat(self.transpose().toAMat()),
    #-----------------------------------------------------------------------
    rChildren := self >> [self.func, self.pkSize],
    rSetChild := rSetChildFields("func", "pkSize"),
    #-----------------------------------------------------------------------
    new := (self, func, pkSize) >> SPL(WithBases(self,
        rec(func       := FF(func),
            pkSize     := pkSize,
            dimensions := [pkSize*func.range(), pkSize*func.domain()]
            ))),
    #-----------------------------------------------------------------------
    transpose := self >> GathMem(self.func, self.pkSize),
    #-----------------------------------------------------------------------
    print := (self,i,is) >> Print(self.name, "(", self.func, ", ", self.pkSize, ")"),
));

Class(GathMemDummy, GathMem);
Class(ScatMemDummy, ScatMem);

#F ==========================================================================
#F MultiBufSum(<var>, <domain>, <scat>, <spl>, <gath>)
#F - Like ISum but makes multibuffered loop in the code
#F ==========================================================================

Class(MultiBufISum, ISum, rec(
    doNotMarkBB := true,
    cannotChangeDataFormat := self >> true, # Because ScatMem/GathMem (hidden) cannot change data format
    #needSeparateVRC := self >> true,

    new := meth(self, var, domain, scatmem, expr, gathmem)
        local res;
        Constraint(IsSPL(expr));
        # if domain is an integer (not symbolic) it must be non-zero
        Constraint(not IsInt(domain) or domain > 0);
        res := SPL(WithBases(self, rec(
               var := var,
               domain := domain,
               scatmem := scatmem,
               _children := [expr],
               gathmem := gathmem
        )));
        res.dimensions := expr.dims()*domain;
        return res;
    end,

    rChildren := self >> [self.scatmem, self._children[1], self.gathmem],

    rSetChild := meth(self, n, what)
       if n=1 then self.scatmem := what;
       elif n=2 then self._children[1] := what;
       elif n=3 then self.gathmem := what;
       fi;
    end,

    from_rChildren := (self, rch) >> ObjId(self)(self.var, self.domain, rch[1], rch[2], rch[3]),

    at := (self, u) >> SubstVars(Copy(self.scatmem*self._children[1]*self.gathmem), rec((self.var.id):=u)),

    dims := (self) >> self._children[1].dims()*self.domain,
    rng := (self) >> self.scatmem.rng(), # Since this is hidden
    dmn := (self) >> self.gathmem.dmn(), # Since this is hidden

    print := meth(self, indent, indentStep)
        local expr, scatmem, gathmem;
        expr := self._children[1];
        scatmem := self.scatmem;
        gathmem := self.gathmem;

        Print(self.name, "(", self.var, ", ", self.domain);
        self._newline(indent + indentStep);
        SPLOps.Print(scatmem, indent+indentStep, indentStep);

        self._newline(indent + indentStep);
        SPLOps.Print(expr, indent+indentStep, indentStep);

        self._newline(indent + indentStep);
        SPLOps.Print(gathmem, indent+indentStep, indentStep);
        self._newline(indent);
        Print(")");
    end

));

#NOTE: this probably needs a needSeparateVRC. Check and add.
Class(MultiBufDistSum, DistSum, rec(
    new := meth(self, var, domain, scatmem, expr, gathmem)
        local res;
        Constraint(IsSPL(expr));
        # if domain is an integer (not symbolic) it must be non-zero
        Constraint(not IsInt(domain) or domain > 0);
        var.isParallelLoopIndex := true;
        res := SPL(WithBases(self, rec(
               var := var,
               domain := domain,
               scatmem := scatmem,
               _children := [expr],
               gathmem := gathmem
        )));
        res.dimensions := expr.dims()*domain;
        return res;
    end,

    rChildren := self >> [self.scatmem, self._children[1], self.gathmem],

    rSetChild := meth(self, n, what)
       if n=1 then self.scatmem := what;
       elif n=2 then self._children[1] := what;
       elif n=3 then self.gathmem := what;
       fi;
    end,

    from_rChildren := (self, rch) >> ObjId(self)(self.var, self.domain, rch[1], rch[2], rch[3]),

    at := (self, u) >> SubstVars(Copy(self.scatmem*self._children[1]*self.gathmem), rec((self.var.id):=u)),

    dims := (self) >> self._children[1].dims()*self.domain,

    print := meth(self, indent, indentStep)
        local expr, scatmem, gathmem;
        expr := self._children[1];
        scatmem := self.scatmem;
        gathmem := self.gathmem;

        Print(self.name, "(", self.var, ", ", self.domain);
        self._newline(indent + indentStep);
        Print(scatmem);

        self._newline(indent + indentStep);
        SPLOps.Print(expr, indent+indentStep, indentStep); #, ", ", self.nt_maps);

        self._newline(indent + indentStep);
        Print(gathmem);
        self._newline(indent);
        Print(")");
    end

));

Class(MultiBufISumFinal, MultiBufISum);
Class(MemISum, MultiBufISum);
Class(MemISumFinal, MemISum);

Class(R2Sum, ISum, rec(
    doNotMarkBB := true,
    cannotChangeDataFormat := self >> true, # Because ScatMem/GathMem (hidden) cannot change data format
    #needSeparateVRC := self >> true,

    new := meth(self, var1, domain1, var2, domain2, expr)
        local res;
        Constraint(IsSPL(expr));
        # if domain is an integer (not symbolic) it must be non-zero
        Constraint(not IsInt(domain1) or domain1 > 0);
        Constraint(not IsInt(domain2) or domain2 > 0);
        res := SPL(WithBases(self, rec(
               var1 := var1,
               domain1 := domain1,
               var2 := var2,
               domain2 := domain2,
               _children := [expr]
        )));
        res.dimensions := expr.dims()*domain1*domain2;
        return res;
    end,

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

    rSetChild := meth(self, n, what)
       if n=1 then self._children[1]  := what;
       fi;
    end,

    from_rChildren := (self, rch) >> ObjId(self)(self.var1, self.domain1, self.var2, self.domain2, rch[1]),

    at := (self, u) >> SubstVars(Copy(self.scatmem*self._children[1]*self.gathmem), rec((self.var.id):=u)),

    dims := (self) >> self._children[1].dims()*self.domain1*self.domain2,

    #rng := (self) >> self.scatmem.rng(), # Since this is hidden
    #dmn := (self) >> self.gathmem.dmn(), # Since this is hidden

    print := meth(self, i, is)
        local expr, scatmem, gathmem;
        expr := self._children[1];
        #scatmem := self.scatmem;
        #gathmem := self.gathmem;

        Print(self.name, "(", self.var1, ", ", self.domain1, ", ", self.var2, ", ", self.domain2);
        #self._newline(i + is);
        #SPLOps.Print(scatmem, i+is, is);

        self._newline(i + is);
        SPLOps.Print(expr, i+is, is);

        #self._newline(i + is);
        #SPLOps.Print(gathmem, i+is, is);
        self._newline(i);
        Print(")");
    end

));


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

ImportAll(paradigms.vector);
ImportAll(paradigms.smp);

Class(RulesFuncSimpScratch,RulesFuncSimp);
RewriteRules(RulesFuncSimpScratch, rec(
    #L(mn,m) o (fBase(m,j) (X) f) -> f (X) fBase(j,m)
    LTensorFlip := ARule(fCompose,
       [ @(1,L), [ @(3,fTensor), @(2).cond(e->range(e) = @(1).val.params[2]), ...] ], e -> [ fTensor(Copy(Drop(@(3).val.children(), 1)), Copy(@(2).val)) ] ),
));

Class(RulesSumsScratch, RulesSums);
RewriteRules(RulesSumsScratch, rec(

    merge_LSKernel := ARule(Compose, [@(1, LSKernel), @(2, LSKernel)],
        e -> [LSKernel(Compose([@(1).val.child(1), @(2).val.child(1)]), @(1).val.mergeInfo(@(1).val.info, @(2).val.info))]),

    mark_DMAGS := Rule([Compose, @(1, Scat), @(2, LSKernel), @(3, Gath)], e -> DMAScat(@(1).val.func) * @(2).val * DMAGath(@(3).val.func) ),

    mark_SWPSum := Rule([DMAFence, [@(1, ISum), @(2)]], e -> let(
            var := @(1).val.var,
            dom := @(1).val.domain,
            comp := Compose(@(2).val._children),
            DMAFence(SWPSum(var, dom, comp)))),

    Scat_DMAFence := ARule( Compose,
       [ @(1, [Scat, Prm, Diag]),
         @(2, [DMAFence, ISum]) ],
        e -> [ CopyFields(@(2).val, rec(
             _children :=  List(@(2).val._children, c -> @(1).val * c),
             dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

    Gath_DMAFence := ARule( Compose,
       [ @(1, [DMAFence, ISum]),
         @(2, [Gath, Prm, Diag]) ],
        e -> [ CopyFields(@(1).val, rec(
                _children := List(@(1).val._children, c -> c * @(2).val),
                dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

    DMAFence_fold := Rule([@(1, DMAFence), @(2, DMAFence)],
        e -> @(2).val),

    Diag_fPrecompute := Rule([Diag, @(2, fPrecompute)], e -> Diag(@(2).val._children[1])),

    Diag_LSKernel := ARule( Compose,
       [ @(1, Diag), @(2, LSKernel) ],
        e -> [LSKernel(@(1).val * @(2).val.child(1), CopyFields(@(2).val.info, rec(opcount := @(2).val.info.opcount + 6 * Rows(@(1).val))))]),

    LSKernel_Diag := ARule( Compose,
        [ @(1, LSKernel), @(2, Diag) ],
        e -> [
            LSKernel(@(1).val.child(1) * @(2).val,
                CopyFields(@(1).val.info,
                    rec(opcount := @(1).val.info.opcount + 6 * Rows(@(2).val))
                )
            )
        ]
    )
));

Class(RulesRCScratch, RulesRC);
RewriteRules(RulesRCScratch, rec(
   RC_ISum := Rule([RC, @(1, ISum)],
        e -> let(s := @(1).val, ISum(s.var, s.domain, RC(s.child(1))))),

   RC_SWPSum := Rule([RC, @(1, SWPSum)],
        e -> let(s := @(1).val, SWPSum(s.var, s.domain, RC(s.child(1))))),

   RC_DMAFence := Rule([RC, @(1, DMAFence)],
        e -> let(s:=@(1).val, DMAFence(RC(s.child(1))))),

   RC_LSKernel := Rule([RC, @(1, LSKernel)],
        e -> let(s:=@(1).val, LSKernel(RC(s.child(1)), s.info))),

   RC_DMAGath := Rule([RC, @(1, DMAGath)], e -> DMAGath(fTensor(@(1).val.func, fId(2)))),

   RC_DMAScat := Rule([RC, @(1, DMAScat)], e -> DMAScat(fTensor(@(1).val.func, fId(2)))),
));

DMAGath.needInterleavedRight := False;
DMAGath.needInterleavedLeft := False;
DMAGath.cannotChangeDataFormat := True;
DMAGath.totallyCannotChangeDataFormat := True;

DMAScat.needInterleavedRight := False;
DMAScat.needInterleavedLeft := False;
DMAScat.cannotChangeDataFormat := True;
DMAScat.totallyCannotChangeDataFormat := True;