# 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);


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];


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

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;


#
# walrus: Computational Methods for Finitely Generated Monoids and Groups
#

## Places
InstallGlobalFunction(NewPlace,
function(loc, c, colour)
    local p;
    p := Objectify(PregroupPlaceType, [loc,c,colour]);
    Add(loc![3], p);
    return p;
end);

InstallMethod(Location
             , "for a pregroup place"
             , [IsPregroupPlaceRep],
             p -> p![1]);

InstallMethod(Relator
             , "for a pregroup place"
             , [IsPregroupPlace],
             p -> Relator(p![1]));

InstallMethod(Letter
             , "for a pregroup place"
             , [IsPregroupPlaceRep],
             p -> p![2]);

InstallMethod(Colour
             , "for a pregroup place"
             , [IsPregroupPlaceRep],
             p -> p![3]);

InstallMethod(NextPlaces
             , "for a pregroup place"
             , [IsPregroupPlaceRep],
function(p)
    if not IsBound(p![6]) then
        p![6] := Places(NextLocation(Location(p)));
    fi;
    return p![6];
end);

AddOrUpdate := function(map, key, val)
    local tmp;

    tmp := map[key];
    if tmp = fail or val < tmp then
        map[key] := val;
    fi;
end;

InstallGlobalFunction(OneStepRedCase,
function(P)
    local Q, Ql
          , b, c, d, x, y
          , Lp
          , v, v1, v2
          , res
          , xi1, xi2, binv, l, onv, pres, vg, xi;

    pres := PregroupPresentationOf(Relator(P));
    res := HashMap(1024);
    vg := VertexGraph(pres);

    c := Letter(P);

    for Q in NextPlaces(P) do
        # same relator, one position up
        Ql := Location(Q);
        b := InLetter(Ql); binv := PregroupInverse(b);
        d := OutLetter(Ql);
        x := Letter(Q);
        v := VertexFor(vg, [b, d, 0]);
        onv := OutNeighboursOfVertex(vg, v);

        for y in IntermultMap(binv) do
            v1 := VertexFor(vg, [y, binv, 1]);
            xi1 := Blob(pres, y, binv, c);
            for v2 in onv do
                #T this check is new, and assuming the paper
                #T is accurate correct
                if DigraphVertexLabel(vg, v2)[1][2] = x then
                    xi2 := Vertex(pres, v1, v, v2);

                    AddOrUpdate(res, [ __ID(Q), 1 ], xi1 + xi2);
                fi;
            od;
        od;
    od;
    # Note this list is not necessarily dense atm.
    return res;
end);

InstallGlobalFunction(OneStepGreenCase,
function(P)
 local L, L2, b, c, loc, pls, is_consoledge, v, v1, v2, xi1, xi2,
       R, R2, P2, P2s, P2T, i, j, next, res, len, n, l, pres, vg,
       P2_loc, P2_inletter, P2_outletter, P2_outletterinv, P2_letter;

    pres := PregroupPresentationOf(Relator(P));
    res := HashMap(1024);
    vg := VertexGraph(pres);

    L := Location(P);
    b := OutLetter(L);
    c := Letter(P);

    # Every place that is instanciated with location that is in an instantiation of
    # a place P'.
    # We're interested in consolidated edges between Relator(P) and Relator(L2)
    # from the locations that P and pls are at.
    #
    # Do we have to check that Relator(P) and Relator(pls) don't entirely cancel?
    for L2 in Locations(pres) do
        R2 := Relator(L2);

        # L2 instantiates place on R2, have to test consolidated
        #    edges between R and R2 starting from L/L2 on R/R2
        #    respectively
        if (InLetter(L2) = PregroupInverse(b))
            and (OutLetter(L2) = c) then

            # We compute all consolidated edge places,
            # we could do this incrementally, but I don't
            # currently see a use in doing so
            P2s := ConsolidatedEdgePlaces(L, L2);

            for P2T in P2s do
                P2 := P2T[1];  # Place reachable on R1 by consolidated edge

                P2_loc := Location(P2);
                P2_inletter := InLetter(P2_loc);
                P2_outletter := OutLetter(P2_loc);
                P2_outletterinv := PregroupInverse(P2_outletter);
                P2_letter := Letter(P2);

                # P2T[2] location on R2 reachable by the edge
                len := P2T[3]; # length of consolidated edge
                v1 := VertexFor(vg, [ InLetter(P2T[2]), OutLetter(P2T[2]), 0 ]);
                v := VertexFor(vg, [ P2_inletter, P2_outletter, 0 ]);
                for v2 in OutNeighboursOfVertex(vg, v) do
                    if DigraphVertexLabel(vg, v2)[1][1] = P2_outletterinv and
                       P2_letter = DigraphVertexLabel(vg, v2)[1][2] then

                        xi1 := Vertex(pres, v1, v, v2);
                        if Colour(P2) = "green" then
                            AddOrUpdate(res, [ __ID(P2), len ], xi1);

                        elif Colour(P2) = "red" then
                            next := OneStepRedCase(P2);
                            # testhack
                            for n in Keys(next) do
                                AddOrUpdate( res, [ n[1], len + 1 ]
                                           , xi1 + next[n] );
                            od;
                        else
                            Error("Invalid colour");
                        fi;
                    else

                    fi;
                od;
            od;
        fi;
    od;
    return res;
end);

InstallMethod(OneStepReachablePlaces
             , "for a pregroup place"
             , [IsPregroupPlaceRep],
function(p)
    if not IsBound(p![7]) then
        if Colour(p) = "red" then
            p![7] := OneStepRedCase(p);
        elif Colour(p) = "green" then
            p![7] := OneStepGreenCase(p);
        else
            Error("Invalid colour for place ", p, "\n");
        fi;
    fi;
    return p![7];
end);

InstallMethod(__ID
             , "for a pregroup place"
             , [IsPregroupPlace],
             p -> p![5]);

InstallMethod(ViewString
             , "for a pregroup place"
             , [IsPregroupPlaceRep],
function(p)
    return STRINGIFY("(", ViewString(p![1]),
                     ",", ViewString(p![2]),
                     ",", ViewString(p![3]),
                     ")");
end);

InstallMethod(\=
             , "for two pregroup places"
             , [IsPregroupPlaceRep, IsPregroupPlaceRep],
function(p1, p2)
    return (p1![1] = p2![1])
           and (p1![2] = p2![2])
           and (p1![3] = p2![3]);
end);