############################################################################
##
##  iterators.gi                   IRREDSOL                 Burkhard Höfling
##
##  Copyright © 2003–2016 Burkhard Höfling
##

############################################################################
##
#F  SelectionIrreducibleSolubleMatrixGroups(n, q, d, indices, orders, blockdims, max)
##
##  selects the subset of <indices> corresponding to those irreducible
##  matrix gropus whose orders are in <orders>, whose minimal block dims are in
##  <blockdims>. if max is true and d = 1, only the maximal soluble groups are returned,
##  if max is false, the non-maximal ones are returned.
##  To ignore one of the parameters orders, blockdims, max, set it to fail
##  If <indices> is fail, all groups are considered.
##
InstallGlobalFunction(SelectionIrreducibleSolubleMatrixGroups,
    function(n0, q0, d, indices, orders, blockdims, max)

        local n, q, p, descs, gddescs, i;

        n := n0/d;
        q := q0^d;

        # info comes from GL(n, q)

        LoadAbsolutelyIrreducibleSolubleGroupData(n, q);

        if indices = fail then
            indices := IndicesIrreducibleSolubleMatrixGroups(n0, q0, d);
        else
            indices := Intersection(indices, IndicesIrreducibleSolubleMatrixGroups(n0, q0, d));
        fi;

        if n = 1 then # from groups of degree 1 - defer to orders

            descs := IRREDSOL_DATA.GROUPS_DIM1 [q];

		 # evaluate max

            if max <> fail then
                if d > 1 then
                    Error("maximality information not available for groups which are not absolutely irreducible");
                fi;

			if max then
			    if Length(descs) in indices then
				  indices := [Length(descs)];
			    else
			        RemoveSet(indices, Length(descs));
                    fi;
			fi;
            fi;

		 # now look at orders
            if orders <> fail then
                indices := Filtered(indices, i -> descs[i][1] in orders);
            fi;

            # block dims

            if blockdims <> fail then
                if d = 1 then
                    if not 1 in blockdims then
                        indices := [];
                    fi;
                else
                    p := SmallestRootInt(q0);
                    i := PositionSet(DivisorsInt(LogInt(q, p)), LogInt(q0, p));
                    indices := Filtered(indices, k -> descs[k][2][i] in blockdims);
                fi;
            fi;

        else # n > 1

            # evluate max

            if max <> fail then
                if max then
                    indices := Intersection(indices, IRREDSOL_DATA.MAX[n][q]);
                else
                    indices := Difference(indices, IRREDSOL_DATA.MAX[n][q]);
                fi;
            fi;

            descs := IRREDSOL_DATA.GROUPS[n][q];
            gddescs := IRREDSOL_DATA.GUARDIANS[n][q];

            if orders <> fail then
                indices := Filtered(indices, i -> descs[i][4] in orders);
            fi;

            if blockdims <> fail then
                if d = 1 then
                    indices := Filtered(indices, i -> gddescs[descs[i][1]][4] in blockdims);
                else
                    p := SmallestRootInt(q0);
                    i := PositionSet(DivisorsInt(LogInt(q, p)), LogInt(q0, p));
                    indices := Filtered(indices, k -> descs[k][3][i] in blockdims);
                fi;
            fi;
        fi;

        MakeImmutable(indices);
        return indices;
    end);

############################################################################
##
#F  OrdersAbsolutelyIrreducibleSolubleMatrixGroups(n, q, blockdims, max)
##
##  returns a set. Each entry is a pair [order, count] describing how many
##  groups of that order are in the data base whose minimal block dims are in <blockdims>
##  if max is true, only the maximal soluble groups are counted, if max is
##  false, the non-maximal ones are returned.
##  To ignore one of the parameters blockdims, max, set it to fail
##
InstallGlobalFunction(OrdersAbsolutelyIrreducibleSolubleMatrixGroups,
    function(n, q, blockdims, max)

        local orders, descs, guardianDescs, desc, guardianDesc, grpinds, i, j, grp, o;

        LoadAbsolutelyIrreducibleSolubleGroupData(n,q);

        if n = 1 then
            if blockdims <> fail and not 1 in blockdims then
                return [];
            fi;
            if max = true then
                return [[IRREDSOL_DATA.GROUPS_DIM1 [q][Length(IRREDSOL_DATA.GROUPS_DIM1 [q])][1],1]];
            else
                orders :=  List(IRREDSOL_DATA.GROUPS_DIM1 [q], d -> [d[1],1]);
                if max = false then
                    Unbind(orders[Length(orders)]);
                fi;
                return orders;
            fi;
        fi;

        descs := IRREDSOL_DATA.GROUPS[n][q];
        guardianDescs := IRREDSOL_DATA.GUARDIANS[n][q];
        if max = true then
            grpinds := IRREDSOL_DATA.MAX[n][q];
        elif max = false then
            grpinds := Difference([1..Length(descs)], IRREDSOL_DATA.MAX[n][q]);
        else
            grpinds := [1..Length(descs)];
        fi;

        orders := [];

        for i in grpinds do
            desc := descs[i]; # description of group
            guardianDesc := guardianDescs[desc[1]]; # and corresponding guardian
            grp := Source (guardianDesc[3]); # pc group isomorphic with guardian
            if (blockdims = fail or guardianDesc[4] in blockdims) then
                o := OrderGroupByCanonicalPcgsByNumber(FamilyPcgs(grp), desc[2]);
                j := PositionSorted(orders, [o,1], function(a, b) return a[1] < b[1]; end);
                if IsBound(orders[j]) and orders[j][1] = o then
                    orders[j][2] := orders[j][2] + 1;
                else
                    Add(orders, [o,1], j);
                fi;
            fi;
        od;
        return orders;
    end);

############################################################################
##
#F  CheckAndExtractArguments(specialfuncs, checks, argl, caller)
##
##  This function tests whether argl is a list of even length in which the
##  entries at odd positions are functions.
##  For special functions in this list (each entry in specialfuncs is a list of synonyms
##  of such functions) it tests whether the following entry in argl satisfies the
##  function in checks corresponding to specailfunc, and that each specialfunc
##  only occurs once (including synonyms).
##
##  The function returns a record with entries specialvalues, functions, and values.
##  if specialvalues[i] is bound, it was the entry following a function in
##  specialfuncs[i]. The functions at odd positions in argl but not in specialfuncs
##  are returned in the record entry functions,
##  the following entries in argl are in the record entry values.
##
InstallGlobalFunction(CheckAndExtractArguments,
    function(specialfuncs, argl, caller)

        local funcs, vals, specialvals, i, f, j;

        if Length(argl) mod 2 <> 0 then
            Error("number of arguments of `", caller, "' must be even");
        fi;
        funcs := [];
        vals := [];
        specialvals := [];
        for i in [1,3..Length(argl)-1] do
            f := argl[i];
            if not IsFunction(f) then
                Error(i, "-th argument in function `", caller, "' must be a function");
            fi;
            if IsHomogeneousList(argl[i+1]) then
                argl[i+1] := Set(argl[i+1]);
            elif IsList(argl[i+1]) then
                argl[i+1] := ShallowCopy (argl[i+1]);
            else
                argl[i+1] := [argl[i+1]];
            fi;
            j := PositionProperty (specialfuncs, funclist -> f in funclist[1]);
            if j = fail then
                Add(funcs, f);
                Add(vals, argl[i+1]);
            elif IsBound(specialvals[j]) then
                Error("there may be only one occurrence of ", f, " in `", caller, "'");
            else
                if not ForAll (argl[i+1], x -> specialfuncs[j][2](x)) then
                    Error("inadmissible value for argument ", i+1, " in `", caller, "'");
                fi;
                specialvals[j] := argl[i+1];
            fi;
        od;
        return rec(specialvalues := specialvals, functions := funcs, values := vals);
    end);

############################################################################
##
#F  IteratorIrreducibleSolubleMatrixGroups(<func_1>, <val_1>, ...)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(IteratorIrreducibleSolubleMatrixGroups,
    function(arg)

        local r, p, q, k, iter, primes;

        r := CheckAndExtractArguments ([
                [[Degree, DegreeOfMatrixGroup, Dimension, DimensionOfMatrixGroup], IsPosInt],
                [[Characteristic, CharacteristicOfField], p -> IsPosInt(p) and IsPrimeInt(p)],
                [[Field, FieldOfMatrixGroup, TraceField], F -> IsField (F) and IsFinite (F)],
                [[Order, Size], IsPosInt],
                [[MinimalBlockDimension, MinimalBlockDimensionOfMatrixGroup], IsPosInt],
                 [[IsPrimitive, IsPrimitiveMatrixGroup, IsLinearlyPrimitive], x -> x in [true, false]],
                [[IsMaximalAbsolutelyIrreducibleSolubleMatrixGroup], x -> x in [true, false]],
                [[IsAbsolutelyIrreducibleMatrixGroup, IsAbsolutelyIrreducible], x -> x in [true, false]],
                [[SplittingField], F -> IsField (F) and IsFinite (F)]
            ],
            arg,
            "IteratorIrreducibleSolubleMatrixGroups");

        if ForAny (r.specialvalues, v -> IsEmpty(v)) then
            return Iterator ([]);
        fi;

        if not IsBound(r.specialvalues[1]) then
            Error("IteratorIrreducibleSolubleMatrixGroups: You must specify the degree(s) of the desired matrix groups");
        fi;

        if not IsBound(r.specialvalues[3]) then
            Error("IteratorIrreducibleSolubleMatrixGroups: You must specify the field(s) of the desired matrix groups");
        fi;

        # set up data for the iterator record

        iter := rec();
        if Length(r.functions) > 0 then
            iter.testfuncs := r.functions;
            iter.testvals := r.values;
        else
            iter.testfuncs := fail;
        fi;

        iter.dims := r.specialvalues[1];

        if IsBound(r.specialvalues[2]) then # if field characteristic is given
            iter.qs := Filtered(r.specialvalues[3], field -> SmallestRootInt(Size(field)) in r.specialvalues[2]);
        else
            iter.qs := List(r.specialvalues[3], Size);
        fi;

        if IsBound(r.specialvalues[4]) then
            iter.orders := r.specialvalues[4];
        else
            iter.orders := fail;
        fi;

        if IsBound(r.specialvalues[5]) then
            iter.blockdims := r.specialvalues[5];
        else
            iter.blockdims := fail;
        fi;

        if IsBound(r.specialvalues[6]) then
            if Length(r.specialvalues[6]) = 1 then
                iter.primitive := r.specialvalues[6][1];
            else
                Info(InfoWarning, 2, "IteratorIrreducibleSolubleMatrixGroups: `IsPrimitiveMatrixGroup' is redundant - will be ignored");
                iter.primitive := fail;
            fi;
        else
            iter.primitive := fail;
        fi;

        if IsBound(r.specialvalues[7]) then
            if Length(r.specialvalues[7]) > 1 then
                Info(InfoWarning, 2, "IteratorIrreducibleSolubleMatrixGroups: `IsMaximalAbsolutelyIrreducibleSolubleMatrixGroup' is redundant");
                iter.max := fail;
            else
                iter.max := r.specialvalues[7][1];
            fi;
        else
            iter.max := fail;
        fi;

        if IsBound(r.specialvalues[8]) then
            if Length(r.specialvalues[8]) > 1 then
                Info(InfoWarning, 2, "IteratorIrreducibleSolubleMatrixGroups: `IsAbsolutelyIrreducibleSolubleMatrixGroup' is redundant");
                iter.absirred := fail;
            else
                iter.absirred := r.specialvalues[8][1];
                if iter.max = true and iter.absirred = false then
                    Info(InfoWarning, 2, "IteratorIrreducibleSolubleMatrixGroups: values of `IsMaximalAbsolutelyIrreducibleSolubleMatrixGroup' ",
                        "and `IsAbsolutelyIrreducibleSolubleMatrixGroup' contradict each other");
                    return Iterator([]);
                fi;
            fi;
        else
            iter.absirred := fail;
        fi;

        if IsBound(r.specialvalues[9]) then
            iter.split := List(r.specialvalues[9], Size);
        else
            iter.split := fail;
        fi;

        if iter.max = true then
            iter.absirred := true;
        fi;

        # set up start position in iterator, always points to data of next group to read
        # if pos is outside indices, then the next value for d, dim and/or q has to be loaded
        # we mark both iter.d and iter.indices as empty, so that they will be reloaded
        # during the first call to IsDoneIterator

        iter.qi := 1;
        iter.dimi := 0;        # this gets incremented during the first call of IsDoneIterator
                                    # which will also load the data for iter.d and iter.indices
        iter.d := [];
        iter.di := 1;          # mark iter.d as empty
        iter.pos := 1;
        iter.indices := [];  # mark iter.indices as empty

        # add iterator methods

        iter.IsDoneIterator := function(iterator)

            # In order for IsDoneIterator to work in this case, we actually have to look
            # for a new group. If we find a group, it is stored for NextIterator

            local blockdims, dim, divs, d, q, min, max, G, H, gensH, repH, gensG, repG;

            # as a safety measure, make sure that IsDoneIterator can be called
            # after it has returned `true'

            if iterator!.qi > Length(iterator!.qs) then
                return true;
            fi;

            # find next group

            while not IsBound(iterator!.nextGroup) do
                while iterator!.pos > Length(iterator!.indices) do

                    iterator!.di := iterator!.di + 1;
                    while iterator!.di > Length(iterator!.d) do

                        # next dimension
                        iterator!.dimi := iterator!.dimi + 1;

                        if iterator!.dimi > Length(iterator!.dims) then
                            iterator!.dimi := 1;
                            iterator!.qi := iterator!.qi + 1;
                            if iterator!.qi > Length(iterator!.qs) then
                                return true;
                            fi;
                        fi;

                        Info(InfoIrredsol, 1, "searching subgroups of GL(", iterator!.dims[iterator!.dimi], ", ",
                            iterator!.qs[iterator!.qi], ")");

                        # we have a new dimension, store its divisors
                        iterator!.divsdim := DivisorsInt(iterator!.dims[iterator!.dimi]);

                        # set up degrees of splitting field for the construction of groups which are not absolutely irreducible

                        iterator!.di := 1;
                        if iterator!.absirred = true then
                            if iterator!.split = fail or iterator!.qs[iterator!.qi] in iterator!.split then
                                iterator!.d := [1];
                            else
                                iterator!.d := [];
                            fi;
                        else
                            if iterator!.split = fail then
                                iterator!.d := ShallowCopy (iterator!.divsdim);
                            else
                                q := iterator!.qs[iterator!.qi];
                                iterator!.d := Filtered(iterator!.divsdim, t -> q^t in iterator!.split);
                            fi;
                            if iterator!.absirred = false then
                                RemoveSet(iterator!.d, 1);
                            fi;
                        fi;
                    od;

                    dim := iterator!.dims[iterator!.dimi];
                    q := iterator!.qs[iterator!.qi];

                    # get relevant indices for new values of n, q, and d

                    d := iterator!.d[iterator!.di];

                    # merge information about block dims and primitivity

                    if iterator!.primitive = true then
                        if iterator!.blockdims = fail or dim in iterator!.blockdims then
                            blockdims := [dim];
                        else
                            continue;
                        fi;
                    else
                        if iterator!.blockdims = fail then
                            blockdims := ShallowCopy (iterator!.divsdim);
                        else
                            blockdims := Intersection(iterator!.divsdim, iterator!.blockdims);
                        fi;
                        if iterator!.primitive = false then
                            RemoveSet(blockdims, dim);
                        fi;
                    fi;

                    if d = 1 then #absolutely irreducible case
                        max := iterator!.max;
                    elif iterator!.max = true then
                        Error("internal error: iterator!.max is true but trying to construct groups which are not abs. irred");
                    else
                        max := fail;
                    fi;

                    Info(InfoIrredsol, 2, "searching subgroups of GL(", dim, ", ", q, ")",
                            " with splitting field GF(",q, "^",d,"), orders: ", iterator!.orders,
                            " and block dims ", blockdims, " max = ", max);

                    if IsAvailableAbsolutelyIrreducibleSolubleGroupData(dim/d, q^d) then
                        iterator!.indices := SelectionIrreducibleSolubleMatrixGroups(
                            dim, q, d, fail, iterator!.orders, blockdims, max);
                    else
                        Error("group data for GL(", dim/d, ", ",q^d,") is not available. Type \'return;\' to skip these subgroups.");
                        iterator!.indices :=  [];
                    fi;
                    iterator!.pos := 1;
                od;
                G := IrreducibleSolubleMatrixGroup(
                    iterator!.dims[iterator!.dimi],
                    iterator!.qs[iterator!.qi],
                    iterator!.d[iterator!.di],
                    iterator!.indices[iterator!.pos]);
                iterator!.pos := iterator!.pos + 1;

                if iterator!.testfuncs = fail or ForAll ([1..Length(iterator!.testfuncs)], i -> iterator!.testfuncs[i](G) in iterator!.testvals[i]) then
                    iterator!.nextGroup := G;
                fi;
            od;
            return false;
        end;
        iter.NextIterator := function(iterator)

            local G;

            if IsDoneIterator (iterator) then
                Error("iterator already at its end");
            else
                G := iterator!.nextGroup;
                Unbind(iterator!.nextGroup);
                return G;
            fi;
        end;
        iter.ShallowCopy := function(iterator)

                local r;

                # we need not copy lists in iterator, since these are never changed in place
                # and are not documented

                r := rec(
                    IsDoneIterator := iterator!.IsDoneIterator,
                    NextIterator := iterator!.NextIterator,
                    ShallowCopy := iterator!.ShallowCopy,
                    dims := iterator!.dims,
                    dimi := iterator!.dimi,
                    qs := iterator!.qs,
                    qi := iterator!.qi,
                    indices := iterator!.indices,
                    pos := iterator!.pos,
                    d := iterator!.d,
                    di := iterator!.di,
                    split := iterator!.split,
                    orders := iterator!.orders,
                    max := iterator!.max,
                    primitive := iterator!.primitive,
                    blockdims := iterator!.blockdims,
                    absirred := iterator!.absirred,
                    testfuncs := iterator!.testfuncs,
                    testvals := iterator!.testvals);

                if IsBound(iterator!.nextGroup) then
                    r.nextGroup := iterator!.nextGroup;
                fi;
                if IsBound(iterator!.divsdim) then
                    r.divsdim := iterator!.divsdim;
                fi;

                return     r;
            end;
        return IteratorByFunctions(iter);
    end);

############################################################################
##
#F  OneIrreducibleSolubleMatrixGroup(<func_1>, <val_1>, ...)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(OneIrreducibleSolubleMatrixGroup,
    function(arg)

        local iter;

        iter := CallFuncList(IteratorIrreducibleSolubleMatrixGroups, arg);
        if IsDoneIterator(iter) then
            return fail;
        else
            return NextIterator(iter);
        fi;
    end);

############################################################################
##
#F  AllIrreducibleSolubleMatrixGroups(<func_1>, <val_1>, ...)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(AllIrreducibleSolubleMatrixGroups,
    function(arg)

        local iter, l, G;

        iter := CallFuncList(IteratorIrreducibleSolubleMatrixGroups, arg);

        l := [];
        for G in iter do
            Add(l, G);
        od;
        return l;
    end);

############################################################################
##
#E
##


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

Class(DTTType, rec(
   isDTTType:=true,
    __call__ := arg >> ApplyFunc(spiral.transforms.DTT, arg)
));

Class(DTT_C3,  DTTType);  Class(DTT_C4,  DTTType);
Class(DTT_S3,  DTTType);  Class(DTT_S4,  DTTType);
Class(DTT_IC3, DTTType);  Class(DTT_IC4, DTTType);
Class(DTT_IS3, DTTType);  Class(DTT_IS4, DTTType);
IsDTTType := x -> IsRec(x) and IsBound(x.isDTTType) and x.isDTTType;

#F DTT(<type>,<size>,<skew-angle>,<is-poly?>,<is-inv?>)
#F    Discrete Trigonometric Transform
#F
Class(DTT, NonTerminal, rec(
    dims := self >> [self.params[2], self.params[2]],
    isReal := self >> true,
    abbrevs := [ (t,N,skew)     -> Checked(IsPosInt(N), IsDTTType(t), [t,N,skew,false]),
             (t,N,skew,poly) -> Checked(IsPosInt(N), IsDTTType(t), [t,N,skew,poly]),
           ],
    terminate := self >> Error("not implemented"),

    SmallRandom := () ->
        [ RandomList([DTT_C3, DTT_S3, DTT_C4, DTT_S4]),
      Random([2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 27, 30, 32]),
      1 / RandomList([2..10]),
      RandomList([true, false]) ]
#  print := (self,i,is) >> let(p:=self.params,Print(p[1],"(",PrintCS(Drop(p, 1)),")")
));


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

_symGreater1 := o -> IsSymbolic(o) or o > 1;

Class(RulesRankedIndf, RuleSet);
RewriteRules(RulesRankedIndf, rec(
   fCompose_flatten := ARule(fCompose, [@(1,fCompose)], e->@(1).val.children()),

   # Degenerate HH rules (HH -> fAdd)
   #HH_to_fAdd := Rule([HH, @, @, @(1).cond(e->e[1]=1 and Sum(e)=1)], e -> fAdd(e.N, e.n, 0)),

   fAddElim := ARule(fCompose,
       [@(1), @(2, fAdd, e -> e.params[3] = 0 and range(e) = range(@(1).val) and domain(e)=domain(@(1).val))],
        e -> [ @(1).val ]),

   HHZ_fAdd := ARule(fCompose, [@(1, HHZ), @(2, [fAdd])], e -> When(@(2).val.params[3]=0, [ @(1).val ],
	[ HHZ(@(1).val.params[1], @(2).val.params[2], @(1).val.params[3] + @(2).val.params[3] * @(1).val.params[4][1], @(1).val.params[4])])),

   HH_fAdd := ARule(fCompose, [@(1, HH), @(2, fAdd)], e ->
	[ HH(@(1).val.params[1], @(2).val.params[2], @(1).val.params[3] + @(2).val.params[3] * @(1).val.params[4][1],
	                           @(1).val.params[4]) ]),

   fAdd_HH := ARule(fCompose, [@(2, fAdd), @(1, HH)], e ->
	[ HH(range(@(2).val), domain(@(1).val), @(1).val.params[3] + @(2).val.params[3], @(1).val.params[4]) ]),

   # Maude:
   #  var b1 b2 m n MM NN : NatExp . var d : Diagf . var i : Indf .
   #  var w ww : NatExpList . var v vv : NeNatExpList . vars gg gf : Genf .
   #  eq  H (    b1, n w) o H (    b2, m ww)  =  H (    b1 + n * b2, (n * m)  (w + lmul(n, ww))) .
   #  eq  HZ(NN, b1, n w) o H (    b2, m ww)  =  HZ(NN, b1 + n * b2, (n * m)  (w + lmul(n, ww))) .
   #  eq  HZ(NN, b1, n w) o HZ(MM, b2, m ww)  =  HZ(NN, b1 + n * b2, (n * m)  (w + lmul(n, ww))) .
   #
   HHoHH := ARule(fCompose, [@(1, HH), @(2, HH)], e -> [ let(
       s1 := @(1).val.params[4], s2 := @(2).val.params[4],
       b1 := @(1).val.params[3], b2 := @(2).val.params[3],
       n := s1[1],  m := s2[1],  w := Drop(s1, 1),  ww := List(Drop(s2, 1), x -> n*x),
       HH(@(1).val.params[1], @(2).val.params[2], b1 + n*b2, Concatenation([n*m], ListAddZP(w, ww)))) ]),

   # This rule is invalid when HH stride is odd, so in general it should not be used
   # NOTE: this rule is needed for DCTs, it seems that in that case stride is always even, so its ok to use it
   #        how to guard this for libraries??
   BHHoHH := ARule(fCompose, [@(1, BHH), @(2, HH, e->_symGreater1(e.params[4][1]))], e -> [ let(
       s1 := @(1).val.params[4], s2 := @(2).val.params[4],
       b1 := @(1).val.params[3], b2 := @(2).val.params[3],
       n := s1[1],  m := s2[1],  w := Drop(s1, 1),  ww := List(Drop(s2, 1), x -> n*x),
       BHH(@(1).val.params[1], @(2).val.params[2], b1 + n*b2, Concatenation([n*m], ListAddZP(w, ww)),
           @(1).val.params[5])) ]),

   #BHH_pull_out_base := Rule([BHH, @, @, @.cond(e->e<>0), @, @], e ->
   #    fCompose(HH(e.params[1], e.params[1], e.params[3], [1]), BHH(e.params[1], e.params[2], 0, e.params[4], e.params[5]-2*e.params[3]))),

   HH_1_ftensor_pull_out := Rule([fTensor, [@(1,fCompose), [@(2,HH), @, @, @, [ListClass, _1]], ...], @(3, fId)], e->
       let(h := @(2).val, id := @(3).val.params[1],
           fCompose(HH(id*h.params[1], id*h.params[2], id*h.params[3], [1]),
                    fTensor(fCompose(Drop(@(1).val.rChildren(), 1)), @(3).val)))),

   # HHxI2_base_pull_out is for closure reduction in autolib RDFT.
   # Commented out to get smaller closure in DFT and it conflicts with HHxfId_base_pull_in rule below
   #HHxI2_base_pull_out := Rule([fTensor, [@(2,HH), @, @, @.cond(e->e<>0), @], @(3, fId)], e->
   #    let(hp := @(2).val.params, id := @(3).val.params[1],
   #        fCompose(HH(id*hp[1], id*hp[1], id*hp[3], [1]),
   #                 fTensor(HH(hp[1], hp[2], 0, hp[4]), @(3).val)))),

   # Pull in HH into fTensor.
   #
   HHxfId_base_pull_in := ARule(fCompose, [[HH, @(1), @(2), @(3), [ListClass, _1]],
       [fTensor, [@(4,HH), @, @, @, @], @(5, fId, x -> ForAll([@(1).val, @(2).val, @(3).val], e -> _divides(x.params[1], e)))]],
       e -> let( t := @(5).val.params[1], hh := @(4).val.params,
                 [fTensor( HH( div(@(1).val, t), hh[2], hh[3] + div(@(3).val, t), hh[4] ), @(5).val )])),

   HHoBHH := ARule(fCompose, [@(1, HH, e->e.params[3]=0 and _symGreater1(e.params[4][1])), @(2, BHH)], e -> [ let(
       s1 := @(1).val.params[4], s2 := @(2).val.params[4],
       b1 := @(1).val.params[3], b2 := @(2).val.params[3],
       n := s1[1],  m := s2[1],  w := Drop(s1, 1),  ww := List(Drop(s2, 1), x -> n*x),
       BHH(@(1).val.params[1], @(2).val.params[2], b1 + n*b2, Concatenation([n*m], ListAddZP(w, ww)),
           n*@(2).val.params[5])) ]),

   HHZoHH := ARule(fCompose, [@(1, HHZ), @(2, HH)], e -> [ let(
	       s1 := @(1).val.params[4],
	       s2 := @(2).val.params[4],
	       b := @(1).val.params[3], bb := @(2).val.params[3],
	       n := s1[1], m := s2[1], w := Drop(s1, 1),  ww := List(Drop(s2, 1), x->n*x),
	       HHZ(@(1).val.params[1], @(2).val.params[2], b + n*bb, Concatenation([n*m], ListAddZP(w, ww)))) ]),

   HHZoHHZ := ARule(fCompose, [@(1, HHZ), @(2, HHZ)], e -> [ let(
	       s1 := @(1).val.params[4],
	       s2 := @(2).val.params[4],
	       b := @(1).val.params[3], bb := @(2).val.params[3],
	       n := s1[1], m := s2[1], w := Drop(s1, 1),  ww := List(Drop(s2, 1), x->n*x),
	       HHZ(@(1).val.params[1], @(2).val.params[2], b + n*bb, Concatenation([n*m], ListAddZP(w, ww)))) ]),

   KHoKH := ARule(fCompose, [@(1, KH),
                            [@(2, KH), @, @, _0, [ListClass, @, _1], [ListClass, _0, _0]]],
       e -> [ let(
	       s1 := @(1).val.params[4],
	       s2 := @(2).val.params[4],
               corr := @(1).val.params[5],
	       b := @(1).val.params[3], bb := @(2).val.params[3],
	       n := s1[1], m := s2[1], w := Drop(s1, 1),  ww := List(Drop(s2, 1), x->n*x),
	       KH(@(1).val.params[1], @(2).val.params[2], b,
                  Concatenation([n*m], ListAddZP(w, ww)), corr)) ]),

   # HHxIoHH assumes @(3) cannot be pulled into fTensor
   HHxIoHH := ARule(fCompose, [[fTensor, [@(1, HH), @, @, @, [ListClass, _1]], @(2, fId)], @(3, HH)],
       e -> let( p := @(1).val.params, n := @(2).val.params[1],
                 [ HH(n*p[1], n*p[2], n*p[3], [1]), @(3).val ] )),

   # eq tr(n, m) o H(0, 1 n) = H(0, m 1) .
   # H.domain must be less or equal to n
   TroHH := ARule(fCompose,
       [@(1, Tr), @(2, HH, e -> let(n := @(1).val.params[1], Cond(AnySyms(n, e.params[2]), n=e.params[2], n>=e.params[2]) and e.params[4][1]=1 and ForAll([e.params[3]]::Drop(e.params[4],1), x -> _divides(n, x))))],
           e -> [ let(
	       n := @(1).val.params[1], m := @(1).val.params[2], b := @(2).val.params[3], str := Drop(@(2).val.params[4], 1),
	       HH(@(2).val.params[1], @(2).val.params[2], b/n, [m] :: List(str, x -> x/n))) ]),

   Refl0_u_HH0 := ARule(fCompose, [@(1, Refl0_u), @(2, HH, e -> e.params[3]=0 and e.params[4]=[1])], e -> [ let(
	       k := @(1).val.params[1],
	       HH(@(1).val.range(), @(2).val.domain(), 0, [k])) ]),

   Refl0_u_HHrest := ARule(fCompose, [@(1, Refl0_u), @(2, HH, e -> e.params[3]=e.params[2] and e.params[4]=[1, @(1).val.params[2]])], e -> [ let(
	       k := @(1).val.params[1],
	       BHH(@(1).val.range(), @(2).val.domain(), 1, [2*k,1], 2*@(1).val.range())) ]),

   Refl0_odd_HH0 := ARule(fCompose, [@(1, Refl0_odd), @(2, HH, e -> e.params[3]=0 and e.params[4]=[1])], e -> [ let(
	       k := @(1).val.params[1],
	       HH(@(1).val.range(), @(2).val.domain(), 0, [2*k+1])) ]),

   Refl0_odd_HHrest := ARule(fCompose, [@(1, Refl0_odd), @(2, HH, e ->  e.params[4]=[1, @(1).val.params[2]])], e -> [ let(
	       k := @(1).val.params[1],
	       BHH(@(1).val.range(), @(2).val.domain(), 1, [2*k+1,1], 2*@(1).val.range())) ]),

   Refl1_HH := ARule(fCompose, [@(1, Refl1), @(2, HH, e -> e.params[3]=0 and e.params[4]=[1, @(1).val.params[2]])], e -> [ let(
	       k := @(1).val.params[1],
	       BHH(@(1).val.range(), @(2).val.domain(), 0, [2*k,1], 2*@(1).val.range() - 1)) ]),

   MMoHH := ARule(fCompose, [@(1, MM), @(2, HH, e -> e.params[3]=0 and e.params[4]=[1, @(1).val.params[1]])], e -> [ let(
	       m := @(1).val.params[2],
	       KH(@(2).val.params[1], @(2).val.params[2], 0, [m, 1], [0,0])) ]),

   fTensor_AxI_HH_stride_1 := ARule(fCompose, [[@(1, fTensor), ..., [fId, @(2)]], # NOTE: assumes d | base
                                        [@(3,HH), @,
                                                  @.cond(e->_dividesUnsafe(@(2).val,e)),
                                                  @.cond(e->_dividesUnsafe(@(2).val,e)),
                                                  [ListClass, _1, ...]]], e ->
       let(p := @(3).val.params, d := @(2).val, vs := Drop(p[4],1), base := p[3],
           [ fTensor( fCompose( fTensor(DropLast(@(1).val.children(), 1)), HH(div(p[1],d), div(p[2],d), div(base,d),
                                  Concatenation([1], List(vs, v -> div(v,d))))), fId(d))])),

   fTensor_AxI_HH_stride_n := ARule(fCompose, # NOTE: check assumptions
       [[@(1,fTensor), ..., [fId, @(2)]],
        [@(3,HH), @, @, _0, [ListClass, @.cond(e->e=@(2).val), _0, _1]]],

       e -> let(
	   d := @(2).val,
	   p := @(3).val.params,
	   ch := @(1).val.children(),
	   A := fTensor(DropLast(ch, 1)),
           [ fCompose(HH(range(A)*d, range(A), 0, [d, 0, 1]), A) ])
   ),

   fTensor_HH_domain1_fId := ARule(fTensor, [ [@(1, HH), @, _1, @, @], @(2, fId) ],
       e -> let(
	   p := @(1).val.params,
	   n := @(2).val.params[1],
	   [ HH(n*p[1], n, n*p[3], [1] :: List(Drop(p[4], 1), x->n*x)) ])
   ),

   HH_domain1_s1_to_fId1 := Rule( [HH, _1, _1, _0, [ListClass, _1]], e -> fId(1) ),

   fTensor_fId_X_HH := ARule(fCompose, [[@(4,fTensor), @(1, fId), @(2)],
                                        [@(3,HH), @, @.cond(e->e=@(2).val.domain()), _0, [ListClass, _1, @.cond(e->e=@(2).val.domain())]]],
       e ->let(p := @(3).val.params, vs := p[4][2],
           [ HH(@(4).val.range(), @(2).val.domain(), 0, [1, vs]), @(2).val ])),

   # This rule is a hack because it changing advdims of SPL. Range of the outer loop is also
   # assumed to be less than @(5).val (though if it's not then there is overlap in the
   # inner loop.
   f2DTensor_HH_HH_HH :=ARule( fCompose, [
       [f2DTensor, [HH, @(1), @(2), @(3), [ListClass, _1]],
                   [HH, @(4), @(5), @(6), [ListClass, _1]]],
       [HH, @(7).cond(x->x=@(2).val*@(5).val or x=@(5).val*@(2).val),
            @(8).cond(x->x=@(2).val),
            _0,
            [ListClass, @(9).cond(x->x=@(5).val), _1]]],
       e -> [HH( @(1).val*@(4).val, @(2).val, @(6).val+@(4).val*@(3).val, [@(4).val, 1] )]),

   # simplified merge_tensors rule
   fTensor_fTensor := ARule(fCompose,
       [[fTensor, @(1), @(2)], [fTensor, @(3), @(4).cond(
                   e->e.range()=@(2).val.domain() or @(3).val.range() = @(1).val.domain())]],
       e -> [ fTensor(fCompose(@(1).val, @(3).val), fCompose(@(2).val, @(4).val)) ]),

   # eq crt(n, m) o H(0, 1 m) = HZ(m * n, 0, m n) .
   # eq crt(n, m) o H(0, m 1) = HZ(m * n, 0, n m) .
   CRToHH := ARule(fCompose, [ [CRT, @(1), @(2), 1, 1],
	                       @(3,HH,e -> e.params[3]=0 and e.params[4] in [[1,@(2).val], [@(2).val, 1]]) ], e -> [ let(
		n := @(1).val,
                m := @(2).val,
		s1 := @(3).val.params[4][1],
		HHZ(@(3).val.params[1], @(3).val.params[2], 0, When(s1=1, [m, n], [n,m]))) ]),

   ## Scat * ISumn,  ISumn * Gath
   Scat_ISumn := ARule(Compose,  [ @(1, Scat), @(2, ISumn) ],
     e -> [ ISumn(@(2).val.domain,
	          Scat(@(1).val.func.upRank()) * @(2).val.child(1)) ]),

   ISumn_Gath  := ARule(Compose, [ @(1, ISumn), @(2, Gath) ],
     e -> [ ISumn(@(1).val.domain,
	          @(1).val.child(1) * Gath(@(2).val.func.upRank())) ]),

   ## Prm * ISumn,  ISumn * Prm
   Prm_ISumn := ARule(Compose,  [ @(1, Prm), @(2, ISumn) ],
     e -> [ ISumn(@(2).val.domain,
	          Prm(@(1).val.func.upRank()) * @(2).val.child(1)) ]),

   ISumn_Prm  := ARule(Compose, [ @(1, ISumn), @(2, Prm) ],
     e -> [ ISumn(@(1).val.domain,
	          @(1).val.child(1) * Prm(@(2).val.func.upRank())) ]),

   ## Diag * ISumn,  ISumn * Diag
   Diag_ISumn := ARule(Compose,  [ @(1, Diag), @(2, ISumn) ],
     e -> [ ISumn(@(2).val.domain,
	          Diag(@(1).val.element.upRank()) * @(2).val.child(1)) ]),

   ISumn_Diag  := ARule(Compose, [ @(1, ISumn), @(2, Diag) ],
     e -> [ ISumn(@(1).val.domain,
	          @(1).val.child(1) * Diag(@(2).val.element.upRank())) ]),

   ##UU
   #Implementing non-zero base would probably require to have a 2D base
   #Implicit requirements 2 has base=0 and both leading dims are the same.
    UU_UU := ARule(fCompose, [@(1, UU), @(2, UU)],
        e-> let(b1X := @(1).val.params[3], b2X := @(2).val.params[3], b1Y := @(1).val.params[4], b2Y := @(2).val.params[4], s1 := @(1).val.params[5], s2 := @(2).val.params[5],
            c2 := @(2).val.params[6], ld := @(1).val.params[7], ss1 := @(1).val.params[8],
            ss2 := @(2).val.params[8], ss:=ListAddLP(When(Length(ss2)>0,Transposed([List(Transposed(ss2)[1],x->x*s1)
                    ,Transposed(ss2)[2]]),[]), ss1),
            [UU(@(1).val.params[1], @(2).val.params[2], b1X + s1*b2X, b1Y+b2Y, s1*s2, c2, ld, ss)])),

#    UU_H := ARule(fCompose, [@(1, UU), @(2, H)],
#        e-> let(b1X := @(1).val.params[3], b2X := @(2).val.params[3], b1Y := @(1).val.params[4], s1 := @(1).val.params[5], s2 := @(2).val.params[4],
#            ld := @(1).val.params[7], ss1 := @(1).val.params[8], c1 := @(1).val.params[6],
#            [UU(@(1).val.params[1], @(2).val.params[2], b1X + s1*b2X, b1Y, s1*s2, c1, ld, ss1)])),

));


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

SSE_2x64f.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_2x64f,
                  p := [  ] ), rec(
                  instr := vunpackhi_2x64f,
                  p := [  ] ) ],
          v := 2,
          vperm := VPerm(L(4, 2), (y, x) -> chain(assign(vref(y, 0, 2), vunpacklo_2x64f(vref(x, 0, 2), vref(x, 2, 2), [  ])), assign(vref(y, 2, 2), vunpackhi_2x64f(vref(x, 0, 2), vref(x, 2, 2), [  ]))), 2, 2) ) ],
  unrules := [  ],
  x_I_vby2 := [ [ rec(
              instr := vunpacklo_2x64f,
              p := [  ] ), rec(
              instr := vshuffle_2x64f,
              p := [ 1, 2 ] ) ], [ rec(
              instr := vshuffle_2x64f,
              p := [ 2, 1 ] ), rec(
              instr := vunpackhi_2x64f,
              p := [  ] ) ] ] ));
SSE_2x64i.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_2x64i,
                  p := [  ] ), rec(
                  instr := vunpackhi_2x64i,
                  p := [  ] ) ],
          v := 2,
          vperm := VPerm(L(4, 2), (y, x) -> chain(assign(vref(y, 0, 2), vunpacklo_2x64i(vref(x, 0, 2), vref(x, 2, 2), [  ])), assign(vref(y, 2, 2), vunpackhi_2x64i(vref(x, 0, 2), vref(x, 2, 2), [  ]))), 2, 2) ) ],
  unrules := [  ],
  x_I_vby2 := [ [ rec(
              instr := vunpacklo_2x64i,
              p := [  ] ), rec(
              instr := vshuffle_2x64i,
              p := [ 1, 2 ] ) ], [ rec(
              instr := vshuffle_2x64i,
              p := [ 2, 1 ] ), rec(
              instr := vunpackhi_2x64i,
              p := [  ] ) ] ] ));
SSE_2x32f.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_2x32f,
                  p := [  ] ), rec(
                  instr := vunpackhi_2x32f,
                  p := [  ] ) ],
          v := 2,
          vperm := VPerm(L(4, 2), (y, x) -> chain(assign(vref(y, 0, 2), vunpacklo_2x32f(vref(x, 0, 2), vref(x, 2, 2), [  ])), assign(vref(y, 2, 2), vunpackhi_2x32f(vref(x, 0, 2), vref(x, 2, 2), [  ]))), 2, 2) ) ],
  unrules := [  ],
  x_I_vby2 := [ [ rec(
              instr := vunpacklo_2x32f,
              p := [  ] ), rec(
              instr := vshuffle_2x32f,
              p := [ 1, 2 ] ) ], [ rec(
              instr := vshuffle_2x32f,
              p := [ 2, 1 ] ), rec(
              instr := vunpackhi_2x32f,
              p := [  ] ) ] ] ));
SSE_4x32f.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_4x32f,
                  p := [  ] ), rec(
                  instr := vunpackhi_4x32f,
                  p := [  ] ) ],
          v := 4,
          vperm := VPerm(L(8, 4), (y, x) -> chain(assign(vref(y, 0, 4), vunpacklo_4x32f(vref(x, 0, 4), vref(x, 4, 4), [  ])), assign(vref(y, 4, 4), vunpackhi_4x32f(vref(x, 0, 4), vref(x, 4, 4), [  ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(2)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vshuffle_4x32f,
                  p := [ 1, 2, 1, 2 ] ), rec(
                  instr := vshuffle_4x32f,
                  p := [ 3, 4, 3, 4 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 1, 2 ])), assign(vref(y, 4, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 3, 4 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vshuffle_4x32f,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle_4x32f,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 4,
          vperm := VPerm(L(8, 2), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 1, 3 ])), assign(vref(y, 4, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 2, 4 ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vshuffle_4x32f,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle_4x32f,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 1, 2 ])), assign(vref(y, 4, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 3, 4 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 1, 3 ])), assign(vref(y, 4, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 2, 4 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_4x32f,
                  p := [  ] ), rec(
                  instr := vunpackhi_4x32f,
                  p := [  ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(Tensor(
                I(2),
                L(4, 2)
              ) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vunpacklo_4x32f(vref(x, 0, 4), vref(x, 4, 4), [  ])), assign(vref(y, 4, 4), vunpackhi_4x32f(vref(x, 0, 4), vref(x, 4, 4), [  ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 1, 2 ])), assign(vref(y, 4, 4), vshuffle_4x32f(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 3, 4 ]))), 4, 2) ) ],
  unrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := rec(
              instr := vushuffle_4x32f,
              p := [ 1, 3, 2, 4 ] ),
          v := 4,
          vperm := VPerm(L(4, 2), (y, x) -> assign(vref(y, 0, 4), vushuffle_4x32f(vref(x, 0, 4), [ 1, 3, 2, 4 ])), 4, 0.90000000000000002) ) ],
  x_I_vby2 := [ [ rec(
              instr := vshuffle_4x32f,
              p := [ 1, 2, 1, 2 ] ), rec(
              instr := vshuffle_4x32f,
              p := [ 1, 2, 3, 4 ] ) ], [ rec(
              instr := vshuffle_4x32f,
              p := [ 3, 4, 1, 2 ] ), rec(
              instr := vshuffle_4x32f,
              p := [ 3, 4, 3, 4 ] ) ] ] ));
SSE_4x32i.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_4x32i,
                  p := [  ] ), rec(
                  instr := vunpackhi_4x32i,
                  p := [  ] ) ],
          v := 4,
          vperm := VPerm(L(8, 4), (y, x) -> chain(assign(vref(y, 0, 4), vunpacklo_4x32i(vref(x, 0, 4), vref(x, 4, 4), [  ])), assign(vref(y, 4, 4), vunpackhi_4x32i(vref(x, 0, 4), vref(x, 4, 4), [  ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(2)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vshuffle_4x32i,
                  p := [ 1, 2, 1, 2 ] ), rec(
                  instr := vshuffle_4x32i,
                  p := [ 3, 4, 3, 4 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 1, 2 ])), assign(vref(y, 4, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 3, 4 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vshuffle_4x32i,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle_4x32i,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 4,
          vperm := VPerm(L(8, 2), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 1, 3 ])), assign(vref(y, 4, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 2, 4 ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vshuffle_4x32i,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle_4x32i,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 1, 2 ])), assign(vref(y, 4, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 3, 4 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 1, 3 ])), assign(vref(y, 4, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 2, 4 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_4x32i,
                  p := [  ] ), rec(
                  instr := vunpackhi_4x32i,
                  p := [  ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(Tensor(
                I(2),
                L(4, 2)
              ) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vunpacklo_4x32i(vref(x, 0, 4), vref(x, 4, 4), [  ])), assign(vref(y, 4, 4), vunpackhi_4x32i(vref(x, 0, 4), vref(x, 4, 4), [  ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 1, 2 ])), assign(vref(y, 4, 4), vshuffle_4x32i(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 3, 4 ]))), 4, 2) ) ],
  unrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := rec(
              instr := vushuffle_4x32i,
              p := [ 1, 3, 2, 4 ] ),
          v := 4,
          vperm := VPerm(L(4, 2), (y, x) -> assign(vref(y, 0, 4), vushuffle_4x32i(vref(x, 0, 4), [ 1, 3, 2, 4 ])), 4, 0.90000000000000002) ) ],
  x_I_vby2 := [ [ rec(
              instr := vshuffle_4x32i,
              p := [ 1, 2, 1, 2 ] ), rec(
              instr := vshuffle_4x32i,
              p := [ 1, 2, 3, 4 ] ) ], [ rec(
              instr := vshuffle_4x32i,
              p := [ 3, 4, 1, 2 ] ), rec(
              instr := vshuffle_4x32i,
              p := [ 3, 4, 3, 4 ] ) ] ] ));
SSE_8x16i.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(16, 8),
              l := 1,
              N := 16,
              n := 8,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_8x16i,
                  p := [  ] ), rec(
                  instr := vunpackhi_8x16i,
                  p := [  ] ) ],
          v := 8,
          vperm := VPerm(L(16, 8), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(8, 4),
                  I(2)
                ),
              l := 1,
              N := 8,
              n := 4,
              r := 2 ),
          instr := [ rec(
                  instr := vunpacklo2_8x16i,
                  p := [  ] ), rec(
                  instr := vunpackhi2_8x16i,
                  p := [  ] ) ],
          v := 8,
          vperm := VPerm(Tensor(
                L(8, 4),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(4)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 4 ),
          instr := [ rec(
                  instr := vunpacklo4_8x16i,
                  p := [  ] ), rec(
                  instr := vunpackhi4_8x16i,
                  p := [  ] ) ],
          v := 8,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(8, 2),
                  I(2)
                ),
              l := 1,
              N := 8,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vshuffle2_8x16i,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle2_8x16i,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 8,
          vperm := VPerm(Tensor(
                L(8, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vshuffle2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [ 1, 3, 1, 3 ])), assign(vref(y, 8, 8), vshuffle2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [ 2, 4, 2, 4 ]))), 8, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2),
                  I(2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vshuffle2_8x16i,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle2_8x16i,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 8,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(4)
              ) *
              Tensor(
                I(2),
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vshuffle2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [ 1, 3, 1, 3 ])), assign(vref(y, 8, 8), vshuffle2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [ 2, 4, 2, 4 ]))), 8, 4.2000000000000002) *
            VTensor(I(2), 8) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(8, 4)
                ),
              l := 2,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_8x16i,
                  p := [  ] ), rec(
                  instr := vunpackhi_8x16i,
                  p := [  ] ) ],
          v := 8,
          vperm := VTensor(I(2), 8) *
            VPerm(Tensor(
                I(2),
                L(8, 4)
              ) *
              Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2),
                  I(2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vunpacklo2_8x16i,
                  p := [  ] ), rec(
                  instr := vunpackhi2_8x16i,
                  p := [  ] ) ],
          v := 8,
          vperm := VTensor(I(2), 8) *
            VPerm(Tensor(
                I(2),
                L(4, 2),
                I(2)
              ) *
              Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi2_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 8), vunpacklo4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ])), assign(vref(y, 8, 8), vunpackhi4_8x16i(vref(x, 0, 8), vref(x, 8, 8), [  ]))), 8, 2) ) ],
  unrules := [ rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(2)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 2 ),
          instr := rec(
              instr := vushuffle2_8x16i,
              p := [ 1, 3, 2, 4 ] ),
          v := 8,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> assign(vref(y, 0, 8), vushuffle2_8x16i(vref(x, 0, 8), [ 1, 3, 2, 4 ])), 8, 0.90000000000000002) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vushufflelo_8x16i,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vushufflehi_8x16i,
                  p := [ 1, 3, 2, 4 ] ) ],
          v := 8,
          vperm := VPerm(Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> Let(t => TempVec(TArray(x.t.t, Eval(v))),
               decl(t, chain(assign(vref(t, 0, Eval(v)), Eval(i1)(vref(x, 0, Eval(v)), Eval(op1.p))), assign(vref(y, 0, Eval(v)), Eval(i2)(vref(t, 0, Eval(v)), Eval(op2.p))))) )
             , 8, 1.8) ) ],
  x_I_vby2 := [ [ rec(
              instr := vunpacklo4_8x16i,
              p := [  ] ), rec(
              instr := vshuffle2_8x16i,
              p := [ 1, 2, 3, 4 ] ) ], [ rec(
              instr := vshuffle2_8x16i,
              p := [ 3, 4, 1, 2 ] ), rec(
              instr := vunpackhi4_8x16i,
              p := [  ] ) ] ] ));
SSE_16x8i.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(32, 16),
              l := 1,
              N := 32,
              n := 16,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VPerm(L(32, 16), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(16, 8),
                  I(2)
                ),
              l := 1,
              N := 16,
              n := 8,
              r := 2 ),
          instr := [ rec(
                  instr := vunpacklo2_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi2_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VPerm(Tensor(
                L(16, 8),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo2_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi2_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(8, 4),
                  I(4)
                ),
              l := 1,
              N := 8,
              n := 4,
              r := 4 ),
          instr := [ rec(
                  instr := vunpacklo4_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi4_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VPerm(Tensor(
                L(8, 4),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(8)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 8 ),
          instr := [ rec(
                  instr := vunpacklo8_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi8_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  L(8, 2),
                  I(4)
                ),
              l := 1,
              N := 8,
              n := 2,
              r := 4 ),
          instr := [ rec(
                  instr := vshuffle4_16x8i,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle4_16x8i,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 16,
          vperm := VPerm(Tensor(
                L(8, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vshuffle4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [ 1, 3, 1, 3 ])), assign(vref(y, 16, 16), vshuffle4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [ 2, 4, 2, 4 ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2),
                  I(4)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 4 ),
          instr := [ rec(
                  instr := vshuffle4_16x8i,
                  p := [ 1, 3, 1, 3 ] ), rec(
                  instr := vshuffle4_16x8i,
                  p := [ 2, 4, 2, 4 ] ) ],
          v := 16,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(8)
              ) *
              Tensor(
                I(2),
                L(4, 2),
                I(4)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vshuffle4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [ 1, 3, 1, 3 ])), assign(vref(y, 16, 16), vshuffle4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [ 2, 4, 2, 4 ]))), 16, 4.2000000000000002) *
            VTensor(I(2), 16) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(16, 8)
                ),
              l := 2,
              N := 16,
              n := 8,
              r := 1 ),
          instr := [ rec(
                  instr := vunpacklo_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VTensor(I(2), 16) *
            VPerm(Tensor(
                I(2),
                L(16, 8)
              ) *
              Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(8, 4),
                  I(2)
                ),
              l := 2,
              N := 8,
              n := 4,
              r := 2 ),
          instr := [ rec(
                  instr := vunpacklo2_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi2_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VTensor(I(2), 16) *
            VPerm(Tensor(
                I(2),
                L(8, 4),
                I(2)
              ) *
              Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo2_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi2_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2),
                  I(4)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 4 ),
          instr := [ rec(
                  instr := vunpacklo4_16x8i,
                  p := [  ] ), rec(
                  instr := vunpackhi4_16x8i,
                  p := [  ] ) ],
          v := 16,
          vperm := VTensor(I(2), 16) *
            VPerm(Tensor(
                I(2),
                L(4, 2),
                I(4)
              ) *
              Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi4_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(8)
              ), (y, x) -> chain(assign(vref(y, 0, 16), vunpacklo8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ])), assign(vref(y, 16, 16), vunpackhi8_16x8i(vref(x, 0, 16), vref(x, 16, 16), [  ]))), 16, 2) ) ],
  unrules := [ rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(4)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 4 ),
          instr := rec(
              instr := vushuffle4_16x8i,
              p := [ 1, 3, 2, 4 ] ),
          v := 16,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(4)
              ), (y, x) -> assign(vref(y, 0, 16), vushuffle4_16x8i(vref(x, 0, 16), [ 1, 3, 2, 4 ])), 16, 0.90000000000000002) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2),
                  I(2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vushufflelo2_16x8i,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vushufflehi2_16x8i,
                  p := [ 1, 3, 2, 4 ] ) ],
          v := 16,
          vperm := VPerm(Tensor(
                I(2),
                L(4, 2),
                I(2)
              ), (y, x) -> Let(t => TempVec(TArray(x.t.t, Eval(v))),
               decl(t, chain(assign(vref(t, 0, Eval(v)), Eval(i1)(vref(x, 0, Eval(v)), Eval(op1.p))), assign(vref(y, 0, Eval(v)), Eval(i2)(vref(t, 0, Eval(v)), Eval(op2.p))))) )
             , 16, 1.8) ) ],
  x_I_vby2 := [ [ rec(
              instr := vunpacklo8_16x8i,
              p := [  ] ), rec(
              instr := vshuffle4_16x8i,
              p := [ 1, 2, 3, 4 ] ) ], [ rec(
              instr := vshuffle4_16x8i,
              p := [ 3, 4, 1, 2 ] ), rec(
              instr := vunpackhi8_16x8i,
              p := [  ] ) ] ] ));


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

# NewLocation : IsPregroupRelator -> Int -> IsPregroupLocation
InstallGlobalFunction(NewLocation,
function(R,i)
    return Objectify(PregroupLocationType, [R, i, [], R[i-1], R[i]]);
end);

InstallMethod(Relator, "for a location",
              [ IsPregroupLocationRep ],
              l -> l![1]);

InstallMethod(Position, "for a location",
              [ IsPregroupLocationRep ],
              l -> l![2]);

InstallMethod(InLetter, "for a location",
              [ IsPregroupLocationRep ],
              l -> l![4] );

InstallMethod(OutLetter, "for a location",
              [ IsPregroupLocationRep ],
              l -> l![5] );

#X Get rid of this
InstallMethod(__ID, "for a location",
              [ IsPregroupLocationRep ],
              l -> l![6]);

# Return list of Places that have this location
InstallMethod(Places, "for a location",
              [ IsPregroupLocationRep ],
function(l)
    return l![3];
end);

InstallMethod(NextLocation, "for a location",
              [ IsPregroupLocationRep ],
function(l)
    local loc;

    # All locations on the relator that l is on
    loc := Locations(l![1]);
    if l![2] < Length(loc) then
        return loc[l![2] + 1];
    else
        return loc[1];
    fi;
end);

InstallMethod(PrevLocation, "for a location",
              [ IsPregroupLocationRep ],
function(l)
    local loc;

    # All locations on the relator that l is on
    loc := Locations(l![1]);
    if l![2] > 1 then
        return loc[l![2] - 1];
    else
        return loc[Length(loc)];
    fi;
end);

# Two locations are the same if they are on the same
# relator at the same index
InstallMethod(\=, "for a location and a location",
              [ IsPregroupLocationRep, IsPregroupLocationRep],
function(l,r)
    return (l![1] = r![1])
           and (l![2] = r![2]);
end);

InstallMethod(PregroupPresentationOf, "for a location",
              [ IsPregroupLocationRep ],
              l -> PregroupPresentationOf(l![1]));

InstallMethod(ViewString, "for a location",
              [ IsPregroupLocationRep ],
function(l)
    return STRINGIFY(ViewString(l![1]), "("
                     , l![2], ","
                     , ViewString(InLetter(l)), ","
                     , ViewString(OutLetter(l)), ")"
                    );
end);


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

NewRulesFor(DFT, rec(
	DFT_tSPL_CT_Mem := rec(
		switch := true,

		applicable := (self, nt) >> nt.params[1] > 2
			and not IsPrime(nt.params[1])
			and nt.hasTag(AMem),

		children := nt -> let(
			memtag := nt.getTag(AMem),
			Map2(
				Filtered(DivisorPairs(nt.params[1]), (m,n) -> n mod memtag.block = 0),
				(m,n) -> [
					TCompose([
						TGrp(TCompose([
							TTensorI(DFT(m, nt.params[2] mod n), n, AVec, AVec),
							TDiag(fPrecompute(Tw1(m*n, n, nt.params[2])))
						])),
						TGrp(TTensorI(DFT(n, nt.params[2] mod n), m, APar, AVec))
					]).withTags(nt.getTags())
				]
			)
		),

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


a := [ 8, 2, 5, 9, 1, 3, 6, 7, 4 ];
# Make a copy (with "b := a;", b and a would point to the same list)
b := ShallowCopy(a);

# Sort in place
Sort(a);
a;
# [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

# Sort without changing the argument
SortedList(b);
# [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
b;
# [ 8, 2, 5, 9, 1, 3, 6, 7, 4 ]


for i in [1, 3 .. 11] do
    Print(i, "\n");
od;

1
3
5
7
9
11


# Built-in, using sets (which are also lists)
a := [ 1, 2, 3, 1, [ 4 ], 5, 5, [4], 6 ];
# [ 1, 2, 3, 1, [ 4 ], 5, 5, [ 4 ], 6 ]
b := Set(a);
# [ 1, 2, 3, 5, 6, [ 4 ] ]
IsSet(b);
# true
IsList(b);
# true


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

reps:=[[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1]],#E_8
[[1,1],[2,1],[10,1],[11,1],[5,1],[6,1],[7,1],[8,1]],#E_8(a_1)
[[1,1],[2,1],[3,1],[10,1],[12,1],[13,1],[14,1],[8,1]],#E_8(a_2)
[[7,1],[8,1],[9,1],[10,1],[11,1],[12,1],[13,1],[19,1]],#E_8(a_3)
[[9,1],[10,1],[11,1],[12,1],[13,1],[14,1],[15,1],[19,1]],#E_8(a_4)
[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1]],#E_7
[[7,1],[8,1],[9,1],[17,1],[18,1],[19,1],[20,1],[21,1]],#E_8(b_4)
[[9,1],[14,1],[15,1],[17,1],[18,1],[19,1],[20,1],[26,1]],#E_8(a_5)
[[1,1],[3,1],[10,1],[11,1],[5,1],[6,1],[7,1]],#E_7(a_1)
[[8,1],[16,1],[17,1],[18,1],[20,1],[21,1],[24,1],[33,1]],#E_8(b_5)
[[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1]],#D_7
[[4,1],[10,1],[12,1],[14,1],[15,1],[26,1],[27,1],[30,1]],#E_8(a_6)
[[1,1],[2,1],[3,1],[10,1],[12,1],[13,1],[14,1]],#E_7(a_2)
[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[8,1]],#E_6+A_1
#[[2,1],[3,1],[10,1],[12,1],[6,1],[7,1],[8,1],[83,1]],#(D_7(a_1))^{(2)}
[[2,1],[3,1],[10,1],[12,1],[6,1],[7,1],[8,1]],#D_7(a_1)
[[15,1],[22,1],[23,1],[24,1],[25,1],[26,1],[27,1],[28,1]],#E_8(b_6)
[[1,1],[4,1],[7,1],[12,1],[13,1],[17,1],[19,1]],#E_7(a_3)
[[1,1],[2,1],[11,1],[12,1],[5,1],[6,1],[8,1]],#E_6(a_1)+A_1
#[[1,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[67,1]],#(A_7)^{(3)}
[[1,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1]],#A_7
[[2,1],[3,1],[10,1],[12,1],[13,1],[14,1],[8,1]],#D_7(a_2)
[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1]],#E_6
[[2,1],[3,1],[4,1],[5,1],[6,1],[7,1]],#D_6
#[[1,1],[2,1],[3,1],[4,1],[5,1],[7,1],[8,1],[51,1]],#(D_5+A_2)^{(2)}
[[1,1],[2,1],[3,1],[4,1],[5,1],[7,1],[8,1]],#D_5+A_2
[[1,1],[2,1],[11,1],[12,1],[5,1],[6,1]],#E_6(a_1)
[[1,1],[4,1],[7,1],[11,1],[25,1],[26,1],[27,1]],#E_7(a_4)
[[1,1],[2,1],[4,1],[5,1],[6,1],[7,1],[8,1]],#A_6+A_1
[[2,1],[3,1],[10,1],[12,1],[6,1],[7,1]],#D_6(a_1)
[[1,1],[3,1],[4,1],[5,1],[6,1],[7,1]],#A_6
[[30,1],[33,1],[35,1],[36,1],[40,1],[45,1],[53,1],[58,1]],#E_8(a_7)
[[1,1],[2,1],[3,1],[4,1],[5,1],[7,1]],#D_5+A_1
[[4,1],[11,1],[14,1],[16,1],[24,1],[25,1],[26,1]],#E_7(a_5)
[[4,1],[6,1],[9,1],[11,1],[18,1],[19,1],[8,1]],#E_6(a_3)+A_1
[[2,1],[3,1],[10,1],[12,1],[13,1],[14,1]],#D_6(a_2)
[[1,1],[10,1],[11,1],[2,1],[5,1],[7,1],[8,1]],#D_5(a_1)+A_2
[[1,1],[3,1],[4,1],[5,1],[6,1],[8,1]],#A_5+A_1
[[1,1],[3,1],[4,1],[2,1],[6,1],[7,1],[8,1]],#A_4+A_3
[[1,1],[2,1],[3,1],[4,1],[5,1]],#D_5
[[4,1],[6,1],[9,1],[11,1],[18,1],[19,1]],#E_6(a_3)
#[[2,1],[3,1],[4,1],[5,1],[7,1],[8,1],[63,1]],#(D_4+A_2)^{(2)}
[[2,1],[3,1],[4,1],[5,1],[7,1],[8,1]],#D_4+A_2
[[1,1],[3,1],[2,1],[5,1],[6,1],[7,1],[8,1]],#A_4+A_2+A_1
[[1,1],[10,1],[11,1],[2,1],[5,1],[7,1]],#D_5(a_1)+A_1
[[1,1],[3,1],[4,1],[5,1],[6,1]],#A_5
[[1,1],[3,1],[4,1],[2,1],[6,1],[7,1]],#A_4+A_2
[[1,1],[3,1],[4,1],[2,1],[6,1],[8,1]],#A_4+2A_1
[[1,1],[10,1],[11,1],[2,1],[5,1]],#D_5(a_1)
[[1,1],[3,1],[4,1],[6,1],[7,1],[8,1]],#2A_3
[[1,1],[3,1],[4,1],[5,1],[7,1]],#A_4+A_1
[[2,1],[3,1],[10,1],[12,1],[7,1],[8,1]],#D_4(a_1)+A_2
[[2,1],[3,1],[4,1],[5,1],[7,1]],#D_4+A_1
[[1,1],[3,1],[2,1],[5,1],[6,1],[7,1]],#A_3+A_2+A_1
[[1,1],[3,1],[4,1],[5,1]],#A_4
#[[1,1],[3,1],[4,1],[6,1],[7,1],[44,1]],#(A_3+A_2)^{(2)}
[[1,1],[3,1],[4,1],[6,1],[7,1]],#A_3+A_2
[[2,1],[3,1],[10,1],[12,1],[7,1]],#D_4(a_1)+A_1
[[1,1],[3,1],[4,1],[6,1],[8,1]],#A_3+2A_1
[[1,1],[3,1],[2,1],[5,1],[6,1],[8,1]],#2A_2+2A_1
[[2,1],[3,1],[4,1],[5,1]],#D_4
[[2,1],[3,1],[10,1],[12,1]],#D_4(a_1)
[[1,1],[3,1],[4,1],[6,1]],#A_3+A_1
[[1,1],[3,1],[2,1],[5,1],[6,1]],#2A_2+A_1
[[1,1],[3,1],[5,1],[6,1]],#2A_2
[[1,1],[3,1],[2,1],[5,1],[7,1]],#A_2+3A_1
[[1,1],[3,1],[4,1]],#A_3
[[1,1],[3,1],[5,1],[7,1]],#A_2+2A_1
[[1,1],[3,1],[5,1]],#A_2+A_1
[[1,1],[2,1],[5,1],[7,1]],#4A_1
[[1,1],[3,1]],#A_2
[[1,1],[2,1],[5,1]],#3A_1
[[1,1],[2,1]],#2A_1
[[1,1]]#A_1
];

labels:=["E_8",
"E_8(a_1)",
"E_8(a_2)",
"E_8(a_3)",
"E_8(a_4)",
"E_7",
"E_8(b_4)",
"E_8(a_5)",
"E_7(a_1)",
"E_8(b_5)",
"D_7",
"E_8(a_6)",
"E_7(a_2)",
"E_6+A_1",
#"(D_7(a_1))^{(2)}",
"D_7(a_1)",
"E_8(b_6)",
"E_7(a_3)",
"E_6(a_1)+A_1",
#"(A_7)^{(3)}",
"A_7",
"D_7(a_2)",
"E_6",
"D_6",
#"(D_5+A_2)^{(2)}",
"D_5+A_2",
"E_6(a_1)",
"E_7(a_4)",
"A_6+A_1",
"D_6(a_1)",
"A_6",
"E_8(a_7)",
"D_5+A_1",
"E_7(a_5)",
"E_6(a_3)+A_1",
"D_6(a_2)",
"D_5(a_1)+A_2",
"A_5+A_1",
"A_4+A_3",
"D_5",
"E_6(a_3)",
#"(D_4+A_2)^{(2)}",
"D_4+A_2",
"A_4+A_2+A_1",
"D_5(a_1)+A_1",
"A_5",
"A_4+A_2",
"A_4+2A_1",
"D_5(a_1)",
"2A_3",
"A_4+A_1",
"D_4(a_1)+A_2",
"D_4+A_1",
"A_3+A_2+A_1",
"A_4",
#"(A_3+A_2)^{(2)}",
"A_3+A_2",
"D_4(a_1)+A_1",
"A_3+2A_1",
"2A_2+2A_1",
"D_4",
"D_4(a_1)",
"A_3+A_1",
"2A_2+A_1",
"2A_2",
"A_2+3A_1",
"A_3",
"A_2+2A_1",
"A_2+A_1",
"4A_1",
"A_2",
"3A_1",
"2A_1",
"A_1"
];

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

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

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

blocks:=[];

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

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


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

# Here we define shorter print functions for common constructs.
# Loading this file will cause more compact printing of formulas,
# but the output can not be pasted back into GAP.
#

Import(spl, code);
var.print := self >> Print(self.id);
fBase.print := (self,i,is) >> Print("[", self.params[2], "]_", self.N);
fTensor.print := FuncClassOper.infix_print(" X ");
fDirsum.print := FuncClassOper.infix_print(" + ");
fCompose.print := FuncClassOper.infix_print(" o ");
gammaTensor.print := FuncClassOper.infix_print(" [#] ");

Value.print := self >> Cond(IsString(self.v), Print("\"",self.v, "\""), Print(self.v));
mId.print := (self,i,is) >> Print("I", self.params[1], ":", self.params[2], "");
fId.print := (self,i,is) >> Print("I", self.params[1]);
Alt.print := (self,i,is) >> Print("<", self.params[1], "|", self.params[2], ">_", self.params[3]);
#Exp.print := self >> self.cprint();