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

Class(HockneyOpts, FFTXConvOpts, rec(
    tags := [],
    operations := rec(Print := s -> Print("<FFTX Hockney options record>"))
));

hockneyOpts := function(arg)
    local opts, maxSize, argrec;
    opts := Copy(FFTXConvOpts);

    argrec := rec(
        globalUnrolling := opts.globalUnrolling,
        prunedBasemaxSize := opts.globalUnrolling / 2
    );

    if IsBound(arg[1].args) and Length(arg[1].args) >= 2 and IsRec(arg[1].args[2]) then
           argrec := CopyFields(argrec, arg[1].args[2]);
    fi;
    opts.globalUnrolling := argrec.globalUnrolling;

    opts.useDeref := false;
    opts.breakdownRules.PRDFT := List([ PRDFT1_Base2, PRDFT1_CT, PRDFT_PD], _noT);
    opts.breakdownRules.IPRDFT := List([ IPRDFT1_Base1, IPRDFT1_Base2, IPRDFT1_CT, IPRDFT_PD], _noT);
    opts.breakdownRules.IPRDFT2 := List([ IPRDFT2_Base1, IPRDFT2_Base2, IPRDFT2_CT], _noT);
    opts.breakdownRules.PRDFT3 := List([ PRDFT3_Base1, PRDFT3_Base2, PRDFT3_CT, PRDFT3_OddToPRDFT1], _noT);
    opts.breakdownRules.URDFT := List([ URDFT1_Base1, URDFT1_Base2, URDFT1_Base4, URDFT1_CT ], _noT);
    opts.breakdownRules.DFT := [DFT_Base, DFT_PD, DFT_CT];
    opts.breakdownRules.IOPrunedMDRConv := [IOPrunedMDRConv_3D_2trip_zyx_freqdata];
    opts.breakdownRules.MDDFT := [ MDDFT_Base, MDDFT_RowCol ];
    opts.breakdownRules.GT := [ GT_NthLoop ];
    opts.breakdownRules.DFT := [ DFT_Base, DFT_CT, DFT_Rader, CopyFields(DFT_GoodThomas, rec(maxSize := 15)), DFT_PD ];
    opts.topTransforms := [DFT, MDDFT, PrunedDFT, PrunedIDFT, IOPrunedConv, PrunedPRDFT, PrunedIPRDFT, TTensorI, IOPrunedMDRConv,
        PRDFT, IPRDFT, PRDFT1, IPRDFT1, PRDFT2, IPRDFT2, PRDFT3, IPRDFT3, TTensorI ];
    opts.breakdownRules.MDRConv := [ MDRConv_3D_2trip_zyx_freqdata ]; # [MDRConv_Base]

    opts.breakdownRules.PrunedPRDFT := [ CopyFields(PrunedPRDFT_base, rec(maxSize := argrec.prunedBasemaxSize)), PrunedPRDFT_CT_rec_block ];
    opts.breakdownRules.PrunedIPRDFT := [ CopyFields(PrunedIPRDFT_base, rec(maxSize := argrec.prunedBasemaxSize)), PrunedIPRDFT_CT_rec_block ];
    opts.breakdownRules.PrunedDFT := [ CopyFields(PrunedDFT_base, rec(maxSize := argrec.prunedBasemaxSize)), PrunedDFT_CT_rec_block ];

    return opts;
end;

hockneyConf := rec(
    defaultName := "defaultHockneyConf",
    defaultOpts := (arg) >> rec(useHockney := true, args := arg),
    confHandler := hockneyOpts
);

fftx.FFTXGlobals.registerConf(hockneyConf);


#############################################################################
##
#W  extend.gi                                                   Karel Dekimpe
#W                                                               Bettina Eick
##

#############################################################################
##
#F FiniteSubgroupsAreCyclic( G )
##
FiniteSubgroupsAreCyclic := function( G )
    local fin, rep, flg;
    fin := FiniteSubgroupClasses( G );
    rep := List( fin, Representative );
    flg := ForAll( rep, IsCyclic );
    return flg;
end;

#############################################################################
##
#F AllActionsHolonomy( G )
##
## Computes all actions of G on Z which can lead to a torsion-free
## extension centralizing Fit(G). Thus all finite subgroups of G and Fit(G)
## centralizes Z and the kernel of the action has index at most 2.
##
AllActionsHolonomy := function( G )
    local mats, acts, fins, reps, gens, U, hom, F, low, H, i;

    # first we add the trivial action
    mats := List( Igs(G), x -> IdentityMat(1));
    acts := [mats];

    # determine the subgroup known to centralize Z
    U := FittingSubgroup(G);

    # determine all subgroups of index 2 in G/U
    hom := NaturalHomomorphismByNormalSubgroup( G, U );
    F   := Image( hom );
    low := LowIndexSubgroupClasses( F, 2 );
    low := List( low, Representative );
    low := List( low, x -> PreImage( hom, x ) );

    # for each subgroup we get one action
    for H in low do
        mats := List( Igs(G), x -> IdentityMat(1));
        for i in [1..Length(Igs(G))] do
            if not Igs(G)[i] in H then
                mats[i] := - mats[i];
            fi;
        od;
        Add( acts, mats );
    od;
    return acts;
end;

#############################################################################
##
#F AllActionsForTorsionFreeExtension( G )
##
## Computes all actions of G on Z which can lead to a torsion-free
## extension centralizing Fit(G). Thus all finite subgroups of G and Fit(G)
## centralizes Z and the kernel of the action has index at most 2.
##
AllActionsForTorsionFreeExtension := function( G )
    local mats, acts, fins, reps, gens, U, hom, F, low, H, i;

    # first we add the trivial action
    mats := List( Igs(G), x -> IdentityMat(1));
    acts := [mats];

    # determine the subgroup known to centralize Z
    fins := FiniteSubgroupClasses( G );
    reps := List( fins, Representative );
    gens := Flat( List( reps, Igs ) );
    Append( gens, GeneratorsOfGroup( FittingSubgroup(G) ) );
    U := NormalClosure( G, Subgroup( G, gens ) );
    if Index( G, U ) = 1 then return acts; fi;

    # determine all subgroups of index 2 in G/U
    hom := NaturalHomomorphismByNormalSubgroup( G, U );
    F   := Image( hom );
    low := LowIndexSubgroupClasses( F, 2 );
    low := List( low, Representative );
    low := List( low, x -> PreImage( hom, x ) );

    # for each subgroup we get one action
    for H in low do
        mats := List( Igs(G), x -> IdentityMat(1));
        for i in [1..Length(Igs(G))] do
            if not Igs(G)[i] in H then
                mats[i] := - mats[i];
            fi;
        od;
        Add( acts, mats );
    od;
    return acts;
end;

#############################################################################
##
#F ExpandedTail( tail, d )
##
ExpandedTail := function( t, d )
    local r, i;
    r := List( [1..d], x -> 0 );
    for i in [1..Length(t)] do
        if IsBound( t[i] ) then r[i] := t[i][1][1]; fi;
    od;
    return r;
end;

#############################################################################
##
#F HasTorsionFreeExtension( G, CR )
##
## Check if G has a torsion-free extension. We assume that all finite
## subgroups of G are cyclic (otherwise, no torsion-free extension exists).
## CR ist a record describing the second cohomology group of G by Z.
##
HasTorsionFreeExtension := function( G, CR )
    local fins, reps, repp, cc, d, subs, U, g, e, p, w, v, i, t, r, pr,
          f, V, spcs, sub;

    # compute the finite subgroups of prime order up to conjugacy
    fins := FiniteSubgroupClasses( G );
    reps := List( fins, Representative );
    repp := Filtered( reps, x -> IsPrime( Size(x) ) );
    if Length( repp ) = 0 then return true; fi;

    # extract second cohomology
    cc := CR.twocohom;

    # for each finite subgroup of prime order compute the corresponding
    # relation on cc.gcc
    subs := [];
    for U in repp do
        g := GeneratorsOfGroup( U )[1];
        e := Exponents( g );
        p := Order( g );
        w := rec( word := e, tail := [] );
        v := ShallowCopy( w );
        for i in [2..p] do
            v := CollectedTwoCR( CR, v, w );
        od;
        r := ExpandedTail( v.tail, Length( CR. enumrels ) );
        sub := NullspaceIntMod( cc.factor.prei, r, p );
        Add( subs, sub );
    od;

    e := Lcm( List( repp, Size ) );
    d := Length( cc.factor.prei );
    return SizeOfUnionMod( subs, e ) < e^d;
end;

#############################################################################
##
#F HasMinimalCentreExtension( G, CR )
##
## Check if there is an extension of G that has minimal centre; that is,
## Z(Fit(E)) = Z. This is only implemented for groups G with abelian
## Fitting subgroup.
##
HasMinimalCentreExtension := function( G, CR )
    local cc, d, t, F, f, g, l, c, i, j, v, w, a, b;

    # get cohomology and generic elements
    cc := CR.twocohom;
    d  := Length( CR.enumrels );
    t  := List( [1..Length(cc.gcc)], x -> Indeterminate( Integers, x ) );
    t  := t * cc.gcc;

    # get Fitting subgroup generators
    F := FittingSubgroup( G );

    # the abelian case is more effective - consider this first
    if IsAbelian( F ) then
        f := GeneratorsOfGroup( F );
        l := Length( f );

        # find tails of commutators
        c := NullMat( l, l );
        for i in [1..l] do
            for j in [i+1..l] do
                v := rec( word := Exponents( f[i]^-1 ), tail := [] );
                w := rec( word := Exponents( f[j]^-1 ), tail := [] );
                v := CollectedTwoCR( CR, v, w );
                w := rec( word := Exponents( f[i] ), tail := [] );
                v := CollectedTwoCR( CR, v, w );
                w := rec( word := Exponents( f[j] ), tail := [] );
                v := CollectedTwoCR( CR, v, w );
                b := ExpandedTail( v.tail, d );
                a := b * t;
                c[i][j] := a;
                c[j][i] := -a;
            od;
        od;

        # get determinant
        c := GenericDeterminantMat( c );
        return Length( ExtRepPolynomialRatFun(c) ) <> 0;
    fi;

    # the non-abelian case
    f := MinimalGeneratingSet( F );
    g := GeneratorsOfGroup( Centre( F ) );
    if Length(f) < Length( g ) then return false; fi;

    # find tails of commutators
    c := NullMat( Length(f), Length(g) );
    for i in [1..Length(f)] do
        for j in [1..Length(g)] do
            v := rec( word := Exponents( f[i]^-1 ), tail := [] );
            w := rec( word := Exponents( g[j]^-1 ), tail := [] );
            v := CollectedTwoCR( CR, v, w );
            w := rec( word := Exponents( f[i] ), tail := [] );
            v := CollectedTwoCR( CR, v, w );
            w := rec( word := Exponents( g[j] ), tail := [] );
            v := CollectedTwoCR( CR, v, w );
            b := ExpandedTail( v.tail, d );
            c[i][j] := b * t;
        od;
    od;

    # get determinant of rxr submats
    Error("not yet implemented");
    c := Determinant( c );
    c := ExtRepPolynomialRatFun( c );
    c := Filtered( c, x -> IsInt(x) );
    return ForAny( c, x -> x <> 0 );
end;

#############################################################################
##
#F HasExtensionOfType( G, torfree, mincent )
##
## Let G be a pcp group. This function checks if G has a torsion-free
## extension or an extension with Z(Fit(H)) = Z$ with the free abelian
## module Z.
##
InstallGlobalFunction( HasExtensionOfType, function( G, torfree, mincent )
    local mats, CR, found, i;

    # if both flags are false, then there is nothing to do
    if not torfree and not mincent then return true; fi;

    # first check if G is torsion-free or has non-cyclic finite subgrps
    Print("    check that all finite subgroups are cyclic\n");
    if not FiniteSubgroupsAreCyclic( G ) then return false; fi;

    # if G is torsion-free, then all extensions of G are torsion-free
    if Length( FiniteSubgroupClasses(G) ) = 1 then torfree := false; fi;
    if not torfree and not mincent then return true; fi;

    # now loop over actions - the trivial action first
    Print("    consider trivial action \n");
    mats := List( Igs(G), x -> IdentityMat( 1 ) );
    CR := CRRecordByMats( G, mats );
    CR.twocohom := TwoCohomologyCR( CR );
    if mincent then
        found := HasMinimalCentreExtension( G, CR );
        #if found <> HasMinimalCentreExtensionByCRRec2( G, CR ) then
        #    Error("something wrong with min cent");
        #fi;
    fi;
    if (not mincent) or (found and torfree) then
        found := HasTorsionFreeExtension( G, CR );
    fi;
    if found then return true; fi;

    # now consider the remaining actions
    mats := AllActionsForTorsionFreeExtension( G );
    for i in [2..Length(mats)] do
        Print("    consider non-trivial action number ",i-1,"\n");
        CR := CRRecordByMats( G, mats[i] );
        CR.twocohom := TwoCohomologyCR( CR );
        if mincent then
            found := HasMinimalCentreExtension( G, CR );
            #if found <> HasMinimalCentreExtensionByCRRec2( G, CR ) then
            #    Error("something wrong with min cent");
            #fi;
        fi;
        if (not mincent) or (found and torfree) then
            found := HasTorsionFreeExtension( G, CR );
        fi;
        if found then return true; fi;
    od;

    # we have not found a suitable extension
    return false;
end );


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

# These options should be used for compiling PRDFT
#
PRDFTDefaults := CopyFields(SpiralDefaults, rec(
	formulaStrategies  := rec(
               sigmaSpl    := [ sigma.StandardSumsRules, sigma.HfuncSumsRules ],
	       rc          := [ sigma.StandardSumsRules, sigma.HfuncSumsRules ],
	       preRC       := [],
               postProcess := []
	),

	compileStrategy := compiler.SimpleCS,
));

# BBDefaults := unr -> CopyFields(SpiralDefaults, rec(
# 	formulaStrategies  := rec(
#                sigmaSpl    := libgen.LibStrategy,
# 	       rc          := sigma.RCStrategy,
# 	       preRC       := [],
#                postProcess := [
# 		   (opts, s) -> BlockSums(unr, s),
# 		   sigma.BBLocalize
# 	       ]
# 	),

# 	# NOTE: memo variables were breaking with other compile strategies
# 	compileStrategy := compiler.NoCSE,
# 	generateInitFunc := true,

# 	codegen := DefaultCodegen,
# 	unparser := CUnparserProg
# ));


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

Import(code); # EvalScalar

_color := (c, str) -> When(LocalConfig.osinfo.useColor(), c(str), str);

_compute_cycles_from_mflops := (flop, flops) -> IntDouble(Double(EvalScalar(fdiv(flop,flops)))*LocalConfig.cpuinfo.freq);
_compute_mflops := (flop, cycles) -> Cond(cycles<>0, IntDouble(Double(EvalScalar(fdiv(flop,cycles)))*LocalConfig.cpuinfo.freq), "#NA");
_compute_gflops := (flop, cycles) -> Cond(cycles<>0, StringDouble("%.3f", (EvalScalar(fdiv(flop,cycles))/1000.0)*LocalConfig.cpuinfo.freq), "#NA");
_compute_gflops_freq := (flop, cycles, freq) -> Cond(cycles<>0, StringDouble("%.3f", (EvalScalar(fdiv(flop,cycles))/1000.0)*freq), "#NA");

_seqPerfStats := function(file, n, cycles, search_time)
    PrintLine("n ", n, "  seq ", BlueStr(cycles), " [cyc]  search ", search_time, " [s]");
   AppendTo(file,
    PrintLine("n ", n, "  seq ", cycles, " [cyc]  search ", search_time, " [s]"));
end;

_seqPerfStatsMflops := function(file, t, flops, cycles, search_time)
    local realFlops;
    if ObjId(flops)=ListClass then realFlops := flops[2]; flops := flops[1]; else realFlops := 0; fi;
    PrintLine(t, "  ", _color(BlueStr, cycles), " [cyc]  ", _color(DarkYellowStr, _compute_mflops(flops, cycles)), " [Mf/s]  ",
     _compute_mflops(realFlops, cycles), " [Mf/s, real]  ", search_time, " [search, s]");
    AppendTo(file,
    PrintLine(t, "  ", cycles, " [cyc]  ", _compute_mflops(flops, cycles), " [Mf/s]  ",  _compute_mflops(realFlops, cycles), " [Mf/s, real]  ", search_time, " [search, s]"));
end;

_seqPerfStatsGflops := function(file, t, freq, flops, cycles, search_time)
    local realFlops;

    if ObjId(flops)=ListClass then realFlops := flops[2]; flops := flops[1]; else realFlops := 0; fi;

    PrintLine(t, "  ", _color(BlueStr, cycles), " [cyc]  ", _color(DarkYellowStr, _compute_gflops_freq(flops, cycles, freq)), " [Gf/s]  ",
     _compute_gflops_freq(realFlops, cycles, freq), " [Gf/s, real]  ", search_time, " [search, s]");
    AppendTo(file,
    PrintLine(t, "  ", cycles, " [cyc]  ", _compute_gflops_freq(flops, cycles, freq), " [Gf/s]  ", _compute_gflops_freq(realFlops, cycles, freq), " [Gf/s, real]  ", search_time, " [search, s]"));
end;

_seqPerfStatsMflopsAcc := function(file, n, flops, cycles, search_time, acc)
    local realFlops;
    if ObjId(flops)=ListClass then realFlops := flops[2]; flops := flops[1]; else realFlops := 0; fi;
    PrintLine("n ", n, "  seq ", _color(BlueStr, cycles), " [cyc] ", _color(DarkYellowStr, _compute_mflops(flops, cycles)), " [Mflop/s] search ",
    _compute_mflops(realFlops, cycles), " [Mflop/s, real] search ", search_time, " [s] accuracy ", -acc, " [dig]");
    AppendTo(file,
    PrintLine("n ", n, "  seq ", cycles, " [cyc] ", _compute_mflops(flops, cycles), " [Mflop/s] search ", _compute_mflops(realFlops, cycles), " [Mflop/s, real] search ",
    search_time, " [s] accuracy ", -acc, " [dig]"));
end;

_seqPerfStatsGflopsAcc := function(file, t, flops, cycles, search_time, acc)
    local digits, realFlops;
    if ObjId(flops)=ListClass then realFlops := flops[2]; flops := flops[1]; else realFlops := 0; fi;
    digits := When(IsFloat(acc), -IntDouble(d_log(1e-16+AbsFloat(acc))/d_log(10)), acc);
    PrintLine(t, "  ", _color(BlueStr, cycles), " [cyc]  ", _color(DarkYellowStr,
    _compute_gflops(flops, cycles)), " [Gf/s]  ", _compute_gflops(realFlops, cycles), " [Gf/s, real]  ", search_time, " [search, s] ", digits, " [dig]");
    AppendTo(file,
    PrintLine(t, "  ", cycles, " [cyc]  ", _compute_gflops(flops, cycles), " [Gf/s]  ", _compute_gflops(realFlops, cycles), " [Gf/s, real]  ",
    search_time, " [search, s] ", digits, " [dig]"));
end;

_parPerfStats := function(file, n, threads, cycles, search_time)
    PrintLine("n ", n, "  threads ", threads, "  par ", _color(BlueStr, cycles), " [cyc]  search ", search_time, " [s]");
   AppendTo(file,
    PrintLine("n ", n, "  threads ", threads, "  par ", cycles, " [cyc]  search ", search_time, " [s]"));
end;

printcount := function(opcount, countrec)
    local n, i, retval;
    retval := "";
    n := 1;
    for i in opcount do
      retval := ConcatenationString(retval, " ", String(i), " ", countrec.printstrings[n]);
      n := n + 1;
    od;
    return(retval);
end;

_seqPerfStatsGflopsCount := function(file, t, flops, cycles, search_time, count, countrec)
    local realFlops;
    if ObjId(flops)=ListClass then realFlops := flops[2]; flops := flops[1]; else realFlops := 0; fi;

    PrintLine(t, "  ", _color(BlueStr, cycles), " [cyc]  ", _color(DarkYellowStr, _compute_gflops(flops, cycles)), " [Gf/s]  ",
     _compute_gflops(realFlops, cycles), " [Gf/s, real]  ", search_time, " [search, s] ", printcount(count, countrec));

    AppendTo(file,
    PrintLine(t, "  ", cycles, " [cyc]  ", _compute_gflops(flops, cycles), " [Gf/s]  ",
     _compute_gflops(realFlops, cycles), " [Gf/s, real]  ", search_time, " [search, s] ", printcount(count, countrec)) );
end;

_seqPerfStatsGflopsAccCount := function(file, t, flops, cycles, search_time, acc, count, countrec)
    local digits, realFlops;
    if ObjId(flops)=ListClass then realFlops := flops[2]; flops := flops[1]; else realFlops := 0; fi;
    digits := When(IsFloat(acc), -IntDouble(d_log(1e-16+AbsFloat(acc))/d_log(10)), acc);

    PrintLine(t, "  ", _color(BlueStr, cycles), " [cyc]  ", _color(DarkYellowStr, _compute_gflops(flops, cycles)), " [Gf/s]  ",
     _compute_gflops(realFlops, cycles), " [Gf/s, real]  ", search_time, " [search, s] ", digits, " [dig]", printcount(count, countrec));

    AppendTo(file,
    PrintLine(t, "  ", cycles, " [cyc]  ", _compute_gflops(flops, cycles), " [Gf/s]  ",
     _compute_gflops(realFlops, cycles), " [Gf/s, real]  ", search_time, " [search, s] ", digits, " [dig]", printcount(count, countrec)) );
end;


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

CUnparser.bin_shl := (self,o,i,is) >> self.pinfix(o.args, "\<\<");
CUnparser.bin_shr := (self,o,i,is) >> self.pinfix(o.args, ">>");
CUnparser.fpmul := (self,o,i,is) >> Print("(",self(o.args[2],i,is), "*", self(o.args[3],i,is), " >> ", self(o.args[1],i,is), ")");
#TDouble.ctype:="int";
#CUnparser.TDouble := (self, vars, i, is) >> Print("int ", self.infix(vars, ", "));

synth_op := [add, sub, bin_shl, arith_shr, mul, neg]; #, addbin_shl, subbin_shl, bin_shlsub];

CheckSynthFormat := result -> true;

RunSynth := function(nums)
    local rfile, numst, result, msg;
    Constraint(ForAll(nums, IsInt));
    rfile := SysTmpName();
    numst := Concatenation(List(nums, x->Concat(StringInt(x), " ")));
    SYS_EXEC(ACM, " ", numst, " -gap > ", rfile);
    result := Try(ReadVal(rfile));
    if result[1] = false  then
        Error(ACM, " did not produce valid result, offending output kept in '", rfile,
              "'\n error message = ", result[2]);
    fi;
    msg := CheckSynthFormat(result[2]);
    if msg <> true then
        Error(ACM, " did not produce result in correct format, offending output kept in '", rfile,
              "'\n ", msg);

    else
        return result[2];
    fi;
end;

Synth := function(x, destvars, targets)
    local rfile, result, cmd, vmap, destreg, numt, dest, dvar, C, exp, op;
    Constraint(ForAll(targets, IsInt));
    Constraint(Length(destvars) = Length(targets));

    numt := Length(targets);
    result := RunSynth(targets);

    C := [];
    vmap := Concatenation([x], destvars);
    for cmd in result do
        Constraint(IsList(cmd) and Length(cmd)=2 and IsInt(cmd[1]) and cmd[1] > 0);
	dest := cmd[1];
	exp := cmd[2];

	# when we hit a target (not intermediate result), vmap will already have an entry
	if dest <= numt then dvar := vmap[1+dest];
	else dvar := var.fresh_t("t", x.t); vmap[1+dest] := dvar;
	fi;

	if IsList(exp) then
	    op := synth_op[exp[1]];
	    if op in [add,sub] then
		exp := op(vmap[1+exp[2]], vmap[1+exp[3]]);
	    elif op in [bin_shl, arith_shr] then
		exp := op(vmap[1+exp[2]], exp[3]);
	    elif op = neg then
		exp := neg(vmap[1+exp[2]]);
	    fi;
	else
	    Constraint(IsInt(exp));
	    exp := vmap[1+exp];
	fi;
	Add(C, assign(dvar, exp));
    od;
    return C;
end;

testSynth := function(targets)
    local C,D,x;
    x := var("x", Y.t.t);
    D := List([1..Length(targets)], i -> nth(Y, i-1));
    C := chain(Synth(x, D, targets));
    return Compile(C, SpiralDefaults);
end;

SynthChain := function(code)
    local c, i, succ, mults, targets, dests, res, mult_code;
    Constraint(IsChain(code));
    res := [];
    for c in code.cmds do
        if ObjId(c)=assign then
	    succ := SuccLoc(c.loc);
	    mults := Filtered(succ, s->IsBound(s.def) and ObjId(s.def.exp) = fpmul
		                       and IsValue(s.def.exp.args[2]) and s.def.exp.args[2].v<>0);
	    #PrintLine(mults);
	    if Length(mults)=0 then
		Add(res,c);
	    else
		dests := List([1..Length(mults)], x -> var.fresh_t("d", TInt));
		#PrintLine(dests);
		targets := List(mults, x->x.def.exp.args[2].v);
		When(targets<>[],PrintLine(targets),0);
   	        # go ahead and synthesize
		mult_code := Synth(c.loc, dests, targets);
		#PrintLine(mult_code);
		#PrintLine("=====");
	        # plug in dests instead of the mults
		for i in [1..Length(mults)] do
	            mults[i].def.exp := arith_shr(dests[i], mults[i].def.exp.args[1]);
		od;
		Add(res, c);
		Append(res, mult_code);
	    fi;
	else
	    Add(res, c);
	fi;
    od;
    return chain(res);
end;

FPStrat := fracbits ->  Concatenation(BaseIndicesCS, [
    MarkDefUse, #
    (c, opts) -> CopyPropagate.fast(c, opts), # --12 kicks out vars used only once or never

    c -> FixedPointCode(c, 32, fracbits),
    Compile.declareVars
]);

SynStrat := fracbits -> Concatenation(BaseIndicesCS, [
    MarkDefUse, #
    (c, opts) -> CopyPropagate.fast(c, opts), # --12 kicks out vars used only once or never
    BinSplit,
    c -> FixedPointCode(c, 32, fracbits),
    MarkDefUse,
    SynthChain,
    Compile.declareVars
]);

# compiler.CodeSums := function ( bksize, sums )
#     local  code;
#     sums := BB(Gath(fId(Rows(sums)))*sums*Gath(fId(Cols(sums))));
#     code := _CodeSums(sums, Y, X);
#     code := RemoveAssignAcc(code);
#     code := BlockUnroll(code);
#     code := DeclareHidden(code);
#     code.dimensions := sums.dimensions;
#     return code;
# end;
# Add(RuleStrategy, x-> Gath(fId(Rows(x)))*x*Gath(fId(Cols(x))));

RewriteRules(code.RulesStrengthReduce, rec(
   arith_shrl := Rule([arith_shr, [bin_shl, @(1), @(2,Value)], @(3, Value)], e ->
       let(l := @(2).val.v, r:=@(3).val.v,
	   Cond(l > r, bin_shl(@(1).val, l-r),
	        l < r, arith_shr(@(1).val, r-l),
		l = r, @(1).val))),
));


#############################################################################
####
##
#W  anupqprop.gi               ANUPQ package                    Werner Nickel
#W                                                                Greg Gamble
##
##  Installs methods for properties and attributes needed for ANUPQ.
##
#Y  Copyright (C) 2001  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
##

#############################################################################
##
#F  SET_PQ_PROPS_AND_ATTRS( <G>, <func> )
##
InstallGlobalFunction( SET_PQ_PROPS_AND_ATTRS, function( G, func )
local S;
  if not( HasIsPGroup(G) and IsPGroup(G) ) then
    Error( "supplied group is not known to be a p-group\n" );
  fi;
  S := PqStandardPresentation(G : Prime := PrimePGroup(G));
  SetIsCapable( G, IsCapable(S) );
  SetNuclearRank( G, NuclearRank(S) );
  SetMultiplicatorRank( G, MultiplicatorRank(S) );
  return func(G);
end );

#############################################################################
##
#M  IsCapable( <G> )
##
InstallMethod( IsCapable, "fp p-groups", true, [ IsFpGroup ], 0,
  G -> SET_PQ_PROPS_AND_ATTRS(G, IsCapable)
);

InstallMethod( IsCapable, "pc p-groups", true, [ IsPcGroup ], 0,
  G -> SET_PQ_PROPS_AND_ATTRS(G, IsCapable)
);

#############################################################################
##
#M  NuclearRank( <G> )
##
InstallMethod( NuclearRank, "fp p-groups", [ IsFpGroup ], 0,
  G -> SET_PQ_PROPS_AND_ATTRS(G, NuclearRank)
);

InstallMethod( NuclearRank, "pc p-groups", [ IsPcGroup ], 0,
  G -> SET_PQ_PROPS_AND_ATTRS(G, NuclearRank)
);

#############################################################################
##
#M  MultiplicatorRank( <G> )
##
InstallMethod( MultiplicatorRank, "fp p-groups", [ IsFpGroup ], 0,
  G -> SET_PQ_PROPS_AND_ATTRS(G, MultiplicatorRank)
);

InstallMethod( MultiplicatorRank, "pc p-groups", [ IsPcGroup ], 0,
  G -> SET_PQ_PROPS_AND_ATTRS(G, MultiplicatorRank)
);

#E  anupqprop.gi  . . . . . . . . . . . . . . . . . . . . . . . . . ends here


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

## Vector Multiplication operator
## VOLMultiplication(<number inputs>, <length inputs>, <length vector>)
Class(VOLMultiplication, RewritableObject, BaseMat, OLBase, rec(
    dims := self >> [self.rChildren()[2] * self.rChildren()[3], StripList(Replicate(self.rChildren()[1], self.rChildren()[2] * self.rChildren()[3]))],
));

#F RCVOLMultiplication(<number inputs>, <length inputs>, <length vector>)
#F   is a vRC(VOLMultiplication(<number inputs>, <length inputs>, <length vector>/2))
#F   inputs/output is in inteleaved complex format: [re1, im1, re2, im2, ...]

Class(RCVOLMultiplication, RewritableObject, BaseMat, OLBase, rec(
    dims := self >> [self.rChildren()[2] * self.rChildren()[3], StripList(Replicate(self.rChildren()[1], self.rChildren()[2] * self.rChildren()[3]))],
));

#F VRCOLMultiplication(<number inputs>, <length inputs>, <length vector>)
#F   inputs/output is in vector inteleaved complex format:
#F     [[re1, re2, ..., re_v], [im1, im2, ..., im_v], [re_(v+1), ...], ...]

Class(VRCOLMultiplication, RewritableObject, BaseMat, OLBase, rec(
    dims := self >> [2 * self.rChildren()[2] * self.rChildren()[3], StripList(Replicate(self.rChildren()[1], 2 * self.rChildren()[2] * self.rChildren()[3]))],
));

## VOLConjMultiplication(<number inputs>, <length inputs>, <length vector>)
Class(VOLConjMultiplication, RewritableObject, BaseMat, OLBase, rec(
    dims := self >> [self.rChildren()[2] * self.rChildren()[3], StripList(Replicate(self.rChildren()[1], self.rChildren()[2] * self.rChildren()[3]))],
));

#F RCVOLConjMultiplication(<complex input length>, <length vector>)
#F   is a vRC(VTensor(OLConjMultiplication(<complex input length>), <length vector>/2))
#F   inputs/output is in inteleaved complex format: [re1, im1, re2, im2, ...]

Class(RCVOLConjMultiplication, RewritableObject, BaseMat, OLBase, rec(
    dims := self >> [self.rChildren()[2] * self.rChildren()[3], StripList(Replicate(self.rChildren()[1], self.rChildren()[2] * self.rChildren()[3]))],
));

#F VRCOLConjMultiplication(<complex input length>, <length vector>)
#F   inputs/output is in vector inteleaved complex format:
#F     [[re1, re2, ..., re_v], [im1, im2, ..., im_v], [re_(v+1), ...], ...]

Class(VRCOLConjMultiplication, RewritableObject, BaseMat, OLBase, rec(
    dims := self >> [2 * self.rChildren()[2] * self.rChildren()[3], StripList(Replicate(self.rChildren()[1], 2 * self.rChildren()[2] * self.rChildren()[3]))],
));


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:=[];
blocks:=[[[55, 1], [45, 1], [39, 1], [27, 3], [19, 1], [9, 1]], [[27, 8], [25, 1], [7, 1]], [[27, 6], [25, 1], [21, 1], [19, 1], [9, 2], [3, 1]], [[27, 4], [25, 1], [19, 1], [18, 2], [15, 1], [12, 2], [9, 2], [3, 1]], [[27, 2], [25, 1], [21, 2], [18, 2], [15, 2], [12, 2], [9, 3], [7, 1], [3, 1]], [[27, 3], [26, 2], [19, 1], [18, 2], [15, 1], [12, 2], [9, 2], [1, 3]], [[27, 1], [21, 1], [19, 2], [18, 4], [15, 1], [9, 8], [3, 1]], [[23, 2], [21, 1], [19, 1], [17, 1], [15, 3], [12, 4], [11, 2], [9, 1], [7, 1], [5, 1], [3, 3]], [[27, 1], [20, 2], [19, 1], [18, 4], [15, 1], [9, 8], [1, 3]], [[19, 1], [18, 4], [15, 1], [12, 2], [9, 12], [7, 1], [3, 1]], [[23, 1], [22, 2], [19, 1], [16, 2], [15, 1], [13, 3], [12, 2], [11, 1], [10, 2], [7, 1], [4, 2], [3, 1], [1, 3]], [[19, 1], [18, 2], [15, 3], [13, 3], [9, 9], [7, 1], [6, 2], [3, 3]], [[19, 1], [18, 4], [15, 1], [12, 2], [9, 11], [8, 2], [1, 3]], [[19, 1], [15, 8], [9, 9], [3, 8], [2, 2]], [[19, 1], [17, 2], [15, 3], [12, 2], [11, 4], [9, 5], [6, 4], [3, 4], [1, 1]], [[9, 25], [8, 2], [2, 2], [1, 3]], [[19, 1], [17, 1], [16, 2], [15, 2], [12, 2], [11, 2], [10, 4], [9, 3], [6, 4], [3, 2], [2, 2], [1, 3]], [[9, 26], [7, 1], [3, 1], [2, 2]], [[9, 25], [8, 2], [3, 1], [2, 2]], [[9, 25], [8, 2], [2, 2], [1, 3]], [[9, 23], [7, 1], [6, 2], [5, 2], [4, 2], [3, 1], [1, 1]], [[19, 1], [15, 8], [9, 9], [3, 7], [1, 7]], [[19, 1], [16, 4], [15, 1], [11, 5], [10, 4], [9, 2], [6, 4], [3, 1], [1, 10]], [[9, 21], [7, 2], [6, 4], [3, 7]], [[9, 26], [7, 1], [1, 7]], [[9, 20], [8, 2], [7, 1], [6, 4], [3, 6], [1, 3]], [[9, 19], [8, 2], [7, 4], [6, 2], [3, 6], [1, 3]], [[9, 19], [8, 4], [6, 4], [3, 5], [1, 6]], [[9, 19], [7, 8], [3, 5], [1, 6]], [[9, 14], [7, 2], [6, 8], [5, 6], [3, 10]], [[9, 19], [8, 2], [7, 4], [6, 2], [3, 1], [2, 6], [1, 6]], [[9, 13], [8, 2], [7, 1], [6, 8], [5, 3], [4, 6], [3, 6], [1, 3]], [[9, 10], [7, 1], [6, 16], [3, 17], [2, 2]], [[9, 13], [7, 5], [6, 4], [5, 5], [4, 8], [3, 3], [1, 6]], [[9, 9], [8, 2], [6, 16], [3, 17], [2, 2]], [[9, 9], [8, 2], [6, 16], [3, 16], [2, 2], [1, 3]], [[9, 7], [8, 2], [7, 6], [6, 6], [5, 6], [4, 6], [3, 10], [2, 2], [1, 3]], [[9, 19], [7, 8], [1, 21]], [[9, 10], [7, 1], [6, 16], [3, 16], [1, 7]], [[9, 8], [7, 7], [6, 12], [3, 16], [1, 7]], [[9, 3], [8, 2], [7, 1], [6, 18], [5, 3], [3, 25]], [[9, 5], [8, 6], [7, 4], [6, 12], [3, 11], [2, 8], [1, 6]], [[9, 9], [8, 2], [6, 16], [3, 15], [1, 10]], [[9, 3], [7, 5], [6, 16], [5, 3], [3, 24], [1, 3]], [[9, 1], [8, 4], [7, 3], [6, 12], [5, 7], [4, 4], [3, 17], [2, 4], [1, 4]], [[9, 3], [8, 8], [7, 6], [6, 10], [3, 8], [2, 8], [1, 15]], [[8, 4], [7, 6], [6, 4], [5, 10], [4, 16], [3, 6], [2, 4], [1, 10]], [[9, 1], [8, 2], [7, 7], [6, 8], [5, 9], [4, 6], [3, 12], [2, 6], [1, 9]], [[7, 1], [6, 14], [5, 6], [3, 42], [1, 1]], [[9, 2], [8, 6], [7, 13], [6, 6], [3, 2], [2, 14], [1, 21]], [[7, 1], [6, 14], [5, 3], [4, 6], [3, 37], [2, 2], [1, 3]], [[9, 1], [7, 11], [5, 21], [3, 11], [1, 24]], [[7, 1], [6, 12], [5, 5], [4, 8], [3, 34], [1, 10]], [[7, 1], [6, 8], [5, 12], [4, 6], [3, 28], [2, 8], [1, 9]], [[7, 1], [6, 6], [5, 11], [4, 14], [3, 19], [2, 12], [1, 13]], [[3, 80], [2, 4]], [[9, 2], [7, 25], [3, 1], [1, 52]], [[7, 1], [6, 2], [5, 24], [3, 27], [1, 28]], [[7, 1], [6, 2], [5, 15], [4, 18], [3, 10], [2, 14], [1, 24]], [[3, 79], [2, 2], [1, 7]], [[3, 78], [1, 14]], [[3, 70], [2, 14], [1, 10]], [[7, 1], [5, 11], [4, 32], [3, 1], [1, 55]], [[3, 65], [2, 16], [1, 21]], [[3, 58], [2, 20], [1, 34]], [[3, 44], [2, 40], [1, 36]], [[3, 57], [1, 77]], [[3, 31], [2, 50], [1, 55]], [[3, 14], [2, 64], [1, 78]], [[3, 1], [2, 56], [1, 133]]];

#ords:=[];
ords:=[ 81, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 9, 9, 9, 9, 27, 27, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 9, 9, 9, 3, 3, 3, 9, 3, 3, 3, 3, 3, 3, 3 ];

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


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

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

################################
#         MACRO_2xf
#

Class(vunpacklo_2xf, _macro_binop_ev, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 2, 1)));

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

Class(vpacklo_2xf, _macro_binop_ev, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [1, 1], 2, 1)));

Class(vpackhi_2xf, _macro_binop_ev, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [2, 2], 2, 1)));

Class(vmulcx_2xf, VecExp_2.binary());
Class(vswapcx_2xf, VecExp_2.unary());

################################
#         MACRO_4xf
#

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

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

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

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

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

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

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

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

Class(vmulcx_4xf, VecExp_4.binary());
Class(vswapcx_4xf, VecExp_4.unary());

################################
#         MACRO_8xf
#

Class(vunpacklo_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 8, 1)));

Class(vunpackhi_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 8, 1)));

Class(vunpacklo2_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 8, 2)));

Class(vunpackhi2_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 8, 2)));

Class(vpacklo_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [1, 3, 5, 7, 1, 3, 5, 7], 8, 1)));

Class(vpackhi_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [2, 4, 6, 8, 2, 4, 6, 8], 8, 1)));

Class(vpacklo2_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [1, 3, 1, 3], 8, 2)));

Class(vpackhi2_8xf, _macro_binop_ev, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, [2, 4, 2, 4], 8, 2)));

Class(vtrcx_8xf, VecExp_8.unary(), rec(
    semantic := (in1, p) -> shuffle(in1, in1, [1, 2, 5, 6, 3, 4, 7, 8], 8, 1),
    ev := self >> self.t.value(self.semantic(self._vval(1), [])).ev()
));

Class(vmulcx_8xf, VecExp_8.binary());
Class(vswapcx_8xf, VecExp_8.unary());

#################################
# Three Op version
#
ThreeOpFromBinOp(
    Class(vunpacklo_2xf_cmd,  assign_cmd, rec( exp_op := vunpacklo_2xf )),
    Class(vunpackhi_2xf_cmd,  assign_cmd, rec( exp_op := vunpackhi_2xf )),
    Class(vpacklo_2xf_cmd,    assign_cmd, rec( exp_op := vpacklo_2xf   )),
    Class(vpackhi_2xf_cmd,    assign_cmd, rec( exp_op := vpackhi_2xf   )),
    Class(vmulcx_2xf_cmd,     assign_cmd, rec( exp_op := vmulcx_2xf    )),

    Class(vunpacklo_4xf_cmd,  assign_cmd, rec( exp_op := vunpacklo_4xf  )),
    Class(vunpackhi_4xf_cmd,  assign_cmd, rec( exp_op := vunpackhi_4xf  )),
    Class(vunpacklo2_4xf_cmd, assign_cmd, rec( exp_op := vunpacklo2_4xf )),
    Class(vunpackhi2_4xf_cmd, assign_cmd, rec( exp_op := vunpackhi2_4xf )),
    Class(vpacklo_4xf_cmd,    assign_cmd, rec( exp_op := vpacklo_4xf    )),
    Class(vpackhi_4xf_cmd,    assign_cmd, rec( exp_op := vpackhi_4xf    )),
    Class(vpacklo2_4xf_cmd,   assign_cmd, rec( exp_op := vpacklo2_4xf   )),
    Class(vpackhi2_4xf_cmd,   assign_cmd, rec( exp_op := vpackhi2_4xf   )),
    Class(vmulcx_4xf_cmd,     assign_cmd, rec( exp_op := vmulcx_4xf     )),

    Class(vunpacklo_8xf_cmd,  assign_cmd, rec( exp_op := vunpacklo_8xf  )),
    Class(vunpackhi_8xf_cmd,  assign_cmd, rec( exp_op := vunpackhi_8xf  )),
    Class(vunpacklo2_8xf_cmd, assign_cmd, rec( exp_op := vunpacklo2_8xf )),
    Class(vunpackhi2_8xf_cmd, assign_cmd, rec( exp_op := vunpackhi2_8xf )),
    Class(vpacklo_8xf_cmd,    assign_cmd, rec( exp_op := vpacklo_8xf    )),
    Class(vpackhi_8xf_cmd,    assign_cmd, rec( exp_op := vpackhi_8xf    )),
    Class(vpacklo2_8xf_cmd,   assign_cmd, rec( exp_op := vpacklo2_8xf   )),
    Class(vpackhi2_8xf_cmd,   assign_cmd, rec( exp_op := vpackhi2_8xf   )),
    Class(vmulcx_8xf_cmd,     assign_cmd, rec( exp_op := vmulcx_8xf     ))
);

Class(vswapcx_4xf_cmd,    assign_cmd, rec( exp_op := vswapcx_4xf    ));
Class(vswapcx_2xf_cmd,    assign_cmd, rec( exp_op := vswapcx_2xf    ));
Class(vswapcx_8xf_cmd,    assign_cmd, rec( exp_op := vswapcx_8xf    ));

Class(vtrcx_8xf_cmd, assign_cmd, rec(exp_op := vtrcx_8xf));

Class(vstore_cmd, assign_cmd);
Class(vload_cmd,  assign_cmd);

RewriteRules(ThreeOpRuleSet, rec(
    assign_to_store := Rule([assign, @(1, nth), @(2)], e -> vstore_cmd(@(1).val.loc, @(1).val.idx, @(2).val)),
    assign_to_load  := Rule([assign, @(1, var), @(2, nth)], e -> vload_cmd(@(1).val, @(2).val.loc, @(2).val.idx)),
));


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

###############################################################################
##
#R  IsTreeHomomorphismRep
##
DeclareRepresentation("IsTreeHomomorphismRep",
                      IsComponentObjectRep and IsAttributeStoringRep,
                      ["states", "perm", "deg"]);

###############################################################################
##
#R  IsTreeHomomorphismFamilyRep
##
DeclareRepresentation("IsTreeHomomorphismFamilyRep",
                      IsComponentObjectRep and IsAttributeStoringRep,
                      ["spher_index", "top_deg"]);

###############################################################################
##
##  AG_CreatedTreeHomomorphismFamilies
##
##  Contains all created TreeHomomorphismFamily objects; for each spherical
##  index there exists one family, to which all objects created with TreeHomomorphism
##  belong.
##
BindGlobal("AG_CreatedTreeHomomorphismFamilies", rec(ind := [], fam := []));

###############################################################################
##
#M  TreeHomomorphismFamily(<sph_ind>)
##
InstallMethod(TreeHomomorphismFamily, [IsRecord],
function(sph_ind)
  local fam, pos;

  sph_ind := AG_ReducedSphericalIndex(sph_ind);
  if sph_ind in AG_CreatedTreeHomomorphismFamilies.ind then
    for fam in AG_CreatedTreeHomomorphismFamilies.fam do
      if fam!.spher_index = sph_ind then
        return fam;
      fi;
    od;
  fi;

  fam := NewFamily(Concatenation("Automorphisms of ", sph_ind.start, ", (", sph_ind.period, ")-tree"),
                   IsTreeHomomorphism, IsTreeHomomorphism,
                   IsTreeHomomorphismFamily and IsTreeHomomorphismFamilyRep);
  fam!.spher_index := sph_ind;
  fam!.top_deg := AG_TopDegreeInSphericalIndex(sph_ind);

  AddSet(AG_CreatedTreeHomomorphismFamilies.ind, sph_ind);
  Add(AG_CreatedTreeHomomorphismFamilies.fam, fam);

  return fam;
end);

###############################################################################
##
#M  TreeHomomorphism (<states>, <tr>)
##
InstallMethod(TreeHomomorphism,
              [IsList and IsTreeHomomorphismCollection, IsObject],
function(states, perm)
  local top_deg, bot_deg, ind, fam, a;

  if not IsPerm(perm) and not IsTransformation(perm) then
    Error("The second argument ",perm, "must be a permutation or transformation");
  fi;

  if perm^-1<>fail and
     ForAll(states, IsTreeAutomorphism)
  then
    return TreeAutomorphism(states, AG_PermFromTransformation(perm));
  fi;

  top_deg := Length(states);

  if IsPerm(perm) then
    if not IsOne(perm) and top_deg < Maximum(MovedPoints(perm)) then
      Error("The root permutation ", perm, " must move only points from 1 to the degree ", top_deg, " of the tree");
    fi;
  else
    if not IsOne(perm) and top_deg < DegreeOfTransformation(perm) then
      Error("The root transformation ", perm, " must move only points from 1 to the degree ", top_deg, " of the tree");
    fi;
  fi;

  bot_deg := DegreeOfTree(states[1]);
  ind := rec(start := [top_deg], period := [bot_deg]);
  fam := TreeHomomorphismFamily(ind);

  return Objectify(NewType(fam, IsTreeHomomorphism and IsTreeHomomorphismRep),
                   rec(states := ShallowCopy(states),
                       perm := perm,
                       deg := top_deg));
end);

###############################################################################
##
#M  TreeHomomorphism (<states_list>, <perm>)
##
InstallMethod(TreeHomomorphism, [IsList, IsTransformation],
function(states, perm)
  local autom, nstates, s;

  autom := fail;

  for s in states do
    if IsTreeHomomorphism(s) then
      autom := s;
      break;
    elif not IsOne(s) then
      Error("Invalid state `", s, "'");
    fi;
  od;

  if autom = fail then
    Error("Can't create an automaton with all trivial states ",
          "without information about the tree");
  fi;

  nstates := List(states, function(s)
                            if IsOne(s) then
                              return One(autom);
                            else
                              return s;
                            fi;
                          end);

  return TreeHomomorphism(nstates, perm);
end);

###############################################################################
##
#M  TreeHomomorphism(<state_1>, <state_2>, ..., <state_n>, <perm>)
##
InstallMethod(TreeHomomorphism, [IsObject, IsObject, IsTransformation],
  function(a1, a2, perm) return TreeHomomorphism([a1, a2], perm); end);
InstallMethod(TreeHomomorphism, [IsObject, IsObject, IsObject, IsTransformation],
  function(a1, a2, a3, perm) return TreeHomomorphism([a1, a2, a3], perm); end);
InstallMethod(TreeHomomorphism, [IsObject, IsObject, IsObject, IsObject, IsTransformation],
  function(a1, a2, a3, a4, perm) return TreeHomomorphism([a1, a2, a3, a4], perm); end);
InstallMethod(TreeHomomorphism, [IsObject, IsObject, IsPerm],
  function(a1, a2, perm) return TreeHomomorphism([a1, a2], perm); end);
InstallMethod(TreeHomomorphism, [IsObject, IsObject, IsObject, IsPerm],
  function(a1, a2, a3, perm) return TreeHomomorphism([a1, a2, a3], perm); end);
InstallMethod(TreeHomomorphism, [IsObject, IsObject, IsObject, IsObject, IsPerm],
  function(a1, a2, a3, a4, perm) return TreeHomomorphism([a1, a2, a3, a4], perm); end);

###############################################################################
##
#M  ViewObj(<a>)
##
InstallMethod(ViewObj, [IsTreeHomomorphism],
function (a)
    local deg, printword, i, perm, states;

    states := Sections(a);
    deg := Length(states);
    perm := TransformationOnLevel(a, 1);

    Print("(");
    for i in [1..deg] do
        View(states[i]);
        if i <> deg then Print(", "); fi;
    od;
    Print(")");
    if not IsOne(perm) then
      AG_PrintTransformation(perm);
    fi;
end);

###############################################################################
##
#M  PrintObj(<a>)
##
InstallMethod(PrintObj, "for [IsTreeHomomorphism and IsTreeHomomorphismRep]",
                             [IsTreeHomomorphism and IsTreeHomomorphismRep],
function (a)
    local deg, i, states, perm;

    states := Sections(a);
    deg := Length(states);
    perm := TransformationOnLevel(a, 1);

    Print("(");
    for i in [1..deg] do
      if IsAutom(a!.states[i]) then
        View(a!.states[i]);
      else
        Print(a!.states[i]);
      fi;
      if i <> deg then Print(", "); fi;
    od;
    Print(")");
    if not IsOne(perm) then
      AG_PrintTransformation(perm);
    fi;
end);

###############################################################################
##
#M  String(<a>)
##
InstallMethod(String, "for [IsTreeHomomorphism]", [IsTreeHomomorphism],
function (a)
    local deg, printword, i, perm, states, str;

    states := Sections(a);
    deg := Length(states);
    perm := TransformationOnLevel(a, 1);
    str:= "(";

    for i in [1..deg] do
        Append(str, String(states[i]));
        if i <> deg then Append(str, ", "); fi;
    od;
    Append(str, ")");
    if not IsOne(perm) then
      Append(str, AG_TransformationString(perm));
    fi;
    return str;
end);

###############################################################################
##
#M  SphericalIndex (<a>)
##
InstallMethod(SphericalIndex, [IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a)
  return FamilyObj(a)!.spher_index;
end);
InstallMethod(TopDegreeOfTree, [IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a)
  return FamilyObj(a)!.top_deg;
end);

###############################################################################
##
#M  TransformationOnLevel (<a>, <k>)
##
InstallMethod(TransformationOnLevelOp, "for [IsTreeHomomorphism, IsPosInt]",
              [IsTreeHomomorphism, IsPosInt],
function(a, k)
  local states, top, first_level, i, j, d1, d2, permuted, p;

  if k = 1 then
    return TransformationOnFirstLevel(a);
  fi;

  # TODO: it is unnesessarily greedy, it could check whether there
  # are trivial permutations below
  d1 := DegreeOfTree(a);
  d2 := 1;
  for i in [2 .. k] do
    d2 := d2 * DegreeOfLevel(a, i);
  od;
  states := Sections(a);
  top := TransformationOnFirstLevel(a);
  first_level := List(states, s -> TransformationOnLevel(s, k-1));
  permuted := [];
  for i in [1..d1] do
    for j in [1..d2] do
      permuted[d2*(i-1) + j] := d2*(i^top - 1) + j^first_level[i];
    od;
  od;

#  p := PermList(permuted);
#  if p = fail then
#    p := Transformation(permuted);
#  fi;
#  return p;

  return Transformation(permuted);
end);

InstallMethod(TransformationOnFirstLevel, [IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a)
  return AsTransformation(a!.perm);
end);

###############################################################################
##
#M  k ^ a
##
InstallMethod(\^, "for [IsPosInt, IsTreeHomomorphism]", [IsPosInt, IsTreeHomomorphism],
function(k, a)
    return k ^ TransformationOnLevel(a, 1);
end);

###############################################################################
##
#M  seq ^ a
##
InstallMethod(\^, "for [IsList, IsTreeHomomorphism]", [IsList, IsTreeHomomorphism],
function(seq, a)
  if Length(seq) = 0 then
    return [];
  elif Length(seq) = 1 then
    return [seq[1]^TransformationOnLevel(a, 1)];
  else
    return Concatenation([seq[1]^TransformationOnLevel(a, 1)],
                         seq{[2..Length(seq)]}^Section(a, seq[1]));
  fi;
end);

# ###############################################################################
# ##
# #M  FixesLevel(<a>, <k>)
# ##
# InstallMethod(FixesLevel, "for [IsTreeHomomorphism, IsPosInt]",
#               [IsTreeHomomorphism, IsPosInt],
# function(a, k)
#   if HasIsSphericallyTransitive(a) then
#     if IsSphericallyTransitive(a) then
#       return false; fi; fi;
#
#   if IsOne(PermOnLevel(a, k)) then
#     Info(InfoAutomGrp, 3, "IsSphericallyTransitive(a): false");
#     Info(InfoAutomGrp, 3, "  a is not transitive on level", k);
#     Info(InfoAutomGrp, 3, "  a = ", a);
#     SetIsSphericallyTransitive(a, false);
#     return true;
#   else
#     return false;
#   fi;
# end);
#
#
# ###############################################################################
# ##
# #M  FixesVertex(<a>, <v>)
# ##
# InstallOtherMethod(FixesVertex,  "for [IsTreeHomomorphism, IsObject]",
#                    [IsTreeHomomorphism, IsObject],
# function(a, v)
#   if HasIsSphericallyTransitive(a) then
#     if IsSphericallyTransitive(a) then
#       Info(InfoAutomGrp, 3, "FixesVertex(a, v): false");
#       Info(InfoAutomGrp, 3, "  IsSphericallyTransitive(a)");
#       Info(InfoAutomGrp, 3, "  a = ", a);
#       return false;
#     fi;
#   fi;
#
#   if v^a = v then
#     Info(InfoAutomGrp, 3, "IsSphericallyTransitive(a): false");
#     Info(InfoAutomGrp, 3, "  a fixes vertex ", v);
#     Info(InfoAutomGrp, 3, "  a = ", a);
#     SetIsSphericallyTransitive(a, false);
#     return true;
#   else
#     return false;
#   fi;
# end);

###############################################################################
##
#M  Section(<a>, <k>)
##
InstallMethod(Section, [IsTreeHomomorphism, IsPosInt],
function(a, k)
  return Sections(a)[k];
end);

InstallMethod(Section, [IsTreeHomomorphism and IsTreeHomomorphismRep, IsPosInt],
function(a, k)
  return a!.states[k];
end);

###############################################################################
##
#M  Section(<a>, <v>)
##
InstallMethod(Section, [IsTreeHomomorphism, IsList],
function(a, v)
  if Length(v) = 1 then
    return Section(a, v[1]);
  else
    return Section(Section(a, v[1]), v{[2..Length(v)]});
  fi;
end);

###############################################################################
##
#M  Sections(<a>)
##
InstallMethod(Sections, [IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a)
  return a!.states;
end);

###############################################################################
##
#M  Sections(a, k)
##
InstallMethod(Sections, "for [IsTreeHomomorphism, IsPosInt]",
                   [IsTreeHomomorphism, IsPosInt],
function(a, level)
  if level = 1 then
    return Sections(a);
  else
    return Concatenation(List(Sections(a), s -> Sections(s, level-1)));
  fi;
end);

InstallMethod(Sections, "for [IsTreeHomomorphism, IsInt and IsZero]", [IsTreeHomomorphism, IsInt and IsZero],
function(a, level)
  return [a];
end);

###############################################################################
##
#M  Decompose(<a>, <k>)
##
InstallMethod(Decompose, "for [IsTreeHomomorphism, IsPosInt]",
              [IsTreeHomomorphism, IsPosInt],
function(a, level)
  return TreeHomomorphism(Sections(a, level), TransformationOnLevel(a, level));
end);

InstallMethod(Decompose, [IsTreeHomomorphism, IsInt and IsZero],
function(a, level)
  return a;
end);

###############################################################################
##
#M  Decompose(<a>)
##
InstallMethod(Decompose, "for [IsTreeHomomorphism]",
              [IsTreeHomomorphism],
function(a)
  return Decompose(a, 1);
end);

###############################################################################
##
#M  IsOne(<a>)
##
InstallMethod(IsOne, "for [IsTreeHomomorphism]",
              [IsTreeHomomorphism],
function(a)
  local s;

  if not IsOne(TransformationOnLevel(a, 1)) then
    return false;
  fi;

  for s in Sections(a) do
    if not IsOne(s) then
      return false;
    fi;
  od;

  return true;
end);

###############################################################################
##
#M  \=(<a1>, <a2>)
##
# TODO: can lead to infinite recursion
InstallMethod(\=, "for [IsTreeHomomorphism, IsTreeHomomorphism]", ReturnTrue,
              [IsTreeHomomorphism, IsTreeHomomorphism],
function(a1, a2)
  return TransformationOnLevel(a1, 1) = TransformationOnLevel(a2, 1) and
          Sections(a1) = Sections(a2);
end);

###############################################################################
##
#M  \<(<a1>, <a2>)
##
InstallMethod(\<, [IsTreeHomomorphism and IsTreeHomomorphismRep,
                   IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a1, a2)
  return AG_TreeHomomorphismCmp(a1, a2) < 0;
end);

###############################################################################
##
##  AG_TreeHomomorphismCmp(a1, a2)
##
##  Global function to be used from IsTreeAutomomorphism too
##
InstallGlobalFunction(AG_TreeHomomorphismCmp,
function(a1, a2)
  local i, cmp;

  cmp := AG_TrCmp(a1!.perm, a2!.perm, a1!.deg);

  if cmp < 0 then
    return -1;
  elif cmp > 0 then
    return 1;
  fi;

  for i in [1..a1!.deg] do
    if a1!.states[i] < a2!.states[i] then
      return -1;
    elif a1!.states[i] > a2!.states[i] then
      return 1;
    fi;
  od;

  return 0;
end);

###############################################################################
##
#M  OneOp(<a>)
##
InstallMethod(OneOp, [IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a)
  return Objectify(NewType(FamilyObj(a), IsTreeHomomorphism and IsTreeHomomorphismRep),
                   rec(states := List([1..a!.deg], i -> One(a!.states[1])),
                       perm := Transformation([1..a!.deg]),
                       deg := a!.deg));
end);

###############################################################################
##
#M  \*(<a1>, <a2>)
##
InstallMethod(\*, [IsTreeHomomorphism and IsTreeHomomorphismRep,
                   IsTreeHomomorphism and IsTreeHomomorphismRep],
function(a1, a2)
  local a;
  a := Objectify(NewType(FamilyObj(a1), IsTreeHomomorphism and IsTreeHomomorphismRep),
        rec(states := List([1..a1!.deg], i -> a1!.states[i] * a2!.states[i^(a1!.perm)]),
            perm := a1!.perm * a2!.perm,
            deg := a1!.deg));
  SetIsActingOnBinaryTree(a, IsActingOnBinaryTree(a1));
  SetIsActingOnRegularTree(a, IsActingOnRegularTree(a1));
  return a;
end);

###############################################################################
##
#M  \[\](<a1>, <a2>)
##
InstallOtherMethod(\[\], [IsTreeHomomorphism, IsPosInt],
function(a, k)
  return Section(a, k);
end);

###############################################################################
##
#M  Representative( <word>, <fam> )
##
InstallMethod(Representative, "for [IsAssocWord, IsTreeHomomorphismFamily]",
              [IsAssocWord, IsTreeHomomorphismFamily],
function( word, fam )
  if IsAutomFamily( fam ) then return Autom( word, fam );
  elif IsSelfSimFamily( fam ) then return SelfSim( word, fam );
  else Error("the family <fam> must be either IsAutomFamily or IsSelfSimFamily");
  fi;
end);

###############################################################################
##
#M  Representative( <word>, <a> )
##
InstallMethod(Representative, "for [IsAssocWord, IsTreeHomomorphism]",
              [IsAssocWord, IsTreeHomomorphism],
function( word, a )
  local fam;
  fam := FamilyObj(a);
  if IsAutomFamily( fam ) then return Autom( word, fam );
  elif IsSelfSimFamily( fam ) then return SelfSim( word, fam );
  else Error("the homomorphism <a> must be either from IsAutomFamily or from IsSelfSimFamily");
  fi;
end);

# ###############################################################################
# ##
# #M  InverseOp(<a>)
# ##
# InstallMethod(InverseOp, "for [IsTreeHomomorphism and IsTreeHomomorphismRep]",
#               [IsTreeHomomorphism and IsTreeHomomorphismRep],
# function(a)
#   local inv;
#   inv := Objectify(NewType(FamilyObj(a), IsTreeHomomorphism and IsTreeHomomorphismRep),
#             rec(states := List([1..a!.deg], i -> a!.states[i^(a!.perm^-1)]^-1),
#                 perm := a!.perm ^ -1,
#                 deg := a!.deg) );
#   SetIsActingOnBinaryTree(inv, IsActingOnBinaryTree(a));
#   SetIsActingOnRegularTree(inv, IsActingOnRegularTree(a));
#   return inv;
# end);
#
# InstallMethod(InverseOp, "for [IsTreeHomomorphism]", [IsTreeHomomorphism],
# function(a)
#   local states, inv_states, perm;
#   states := Sections(a);
#   perm := Inverse(Perm(a));
#   inv_states := List([1..Length(states)], i -> Inverse(states[i^perm]));
#   return TreeHomomorphism(inv_states, perm);
# end);

#E


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

# VGath_sv/VScat_sv may have bigger dimensions than their function domain so we need padding
# to have Diag with correct dimensions when commuting diag through VGath_sv and VScat_sv
_fComposeVGathScatSv_Diag := (m, dim, d) -> let(
    g := fTensor(m.func, fId(m.sv)),
    mdim := dim(m),
    rem := Cond(m.rem _is Unk, g.domain() mod mdim, m.rem=0, 0, m.v - m.rem * m.sv),
    f := fCompose(d.element, g),
    When(rem = 0, f, diagDirsum(f, fConst(TReal, mdim-rem, 0.0)))
);

Class(RulesVDiag, RuleSet);
RulesVDiag.__transparent__ := [ [Compose, [vRC]] ];  # creates extra rules, A*B->C is made into vRC(A)*vRC(B) -> vRC(C)

RewriteRules(RulesVDiag, rec(
    DiagConst_AnyScat := ARule(Compose, [[Diag, [fConst, @(1), @(2), @(3)]], @(4).cond(IsVScat)],
	e -> [ @(4).val, VDiag(fConst(@(1).val, Cols(@(4).val), @(3).val), getV(@(4).val))]),

    AnyGath_DiagConst := ARule(Compose, [@(4).cond(IsVGath), [Diag, [fConst, @(1), @(2), @(3)]]],
	e -> [ VDiag(fConst(@(1).val, Rows(@(4).val), @(3).val), getV(@(4).val)), @(4).val]),

    DiagConst_NoDiagPullin := ARule(Compose, [[Diag, [fConst, @(1), @(2), @(3)]], @(4, NoDiagPullin)],
	e -> [ @(4).val, Diag(fConst(@(1).val, Cols(@(4).val), @(3).val))]),

    # VGath * IDirSum
     CommuteVGathIDirSum := ARule( Compose,
	 [ [@(1, VGath), @(3)],
	   [@(0, [PushL, PushLR]), @(4, IDirSum, e -> (@(1).val.v mod Rows(e.child(1))) = 0)] ],
       e -> let(
             ratio := @(1).val.v / Rows(@(4).val.child(1)),
             f := When(ratio=1, @(3).val, fTensor(@(3).val, fId(ratio))),
             j := @(4).val.var,
             jj := Ind(f.domain()),

             [ ObjId(@(0).val)(IDirSum(jj,
                   Data(j, f.at(jj), SubstTopDown(@(4).val.child(1), @(5,fBase,e->e.params[2]=j),
                                       e -> fCompose(f, fBase(jj)))))),
               @(1).val ])
    ),

    # IDirSum * VScat
    CommuteIDirSumVScat := ARule( Compose,
       [ [@(0,[PushR, PushLR]), @(4, IDirSum)],
         [@(1, VScat, e -> (e.v mod Cols(@(4).val.child(1)))=0), @(3)] ],

       e -> let(
             ratio := @(1).val.v / Cols(@(4).val.child(1)),
             f := When(ratio=1, @(3).val, fTensor(@(3).val, fId(ratio))),
             j := @(4).val.var,
             jj := Ind(f.domain()),

             [ @(1).val,
               ObjId(@(0).val)(IDirSum(jj,
                   Data(j, f.at(jj), SubstTopDown(@(4).val.child(1), @(5,fBase,e->e.params[2]=j),
                                       e -> fCompose(f, fBase(jj)))))) ])
    ),

    # Gath * RCDiag
    CommuteVGathVRCDiag_fTensor := ARule( Compose,
       [ [@(1, VGath), [@(0,fTensor), ..., [fId, @(2).cond(_isEven)]]], @(4, VRCDiag) ],
       e -> [ VRCDiag(fCompose(@(4).val.element, @(0).val), @(4).val.v), @(1).val ]),

    # Gath * RCDiag
    CommuteVGathVRCDiag_H := ARule( Compose,
       [ [@(1, VGath), [@(2, H), @, @.cond(_isEven), @.cond(_isEven), _1]], @(4, VRCDiag) ],
       e -> [ VRCDiag(fCompose(@(4).val.element, @(2).val), @(4).val.v), @(1).val ]),

    # RCVDiag * VScat
    CommuteRCVDiagVScat := ARule( Compose,
       [ @(1, RCVDiag), @(2, VScat) ], # <-1 <-2 o
        e -> [ @2.val, RCVDiag(fCompose(@1.val.element, @2.val.func), @2.val.v).attrs(@(1).val) ]),

    # RCVDiagSplit * VScat
    CommuteRCVDiagSplitVScat := ARule( Compose,
       [ @(1, RCVDiagSplit), @(2, VScat) ], # <-1 <-2 o
        e -> [ @2.val, RCVDiagSplit(fCompose(@1.val.element, fTensor(@2.val.func, fId(2))), @2.val.v).attrs(@(1).val) ]),

    # VRCDiag * VScat
    CommuteVRCDiagVScat_fTensor := ARule( Compose,
        [ @(4, VRCDiag), [@(1, VScat), [@(0,fTensor), ..., [fId, @(2).cond(_isEven)]]] ],
        e -> [ @(1).val, VRCDiag(fCompose(@(4).val.element, @(0).val), @(4).val.v) ]),

    # VRCDiag * VScat
    CommuteVRCDiagVScat_H := ARule( Compose,
        [ @(4, VRCDiag), [@(1, VScat), [@(2, H), @, @.cond(_isEven), @.cond(_isEven), _1]] ],
        e -> [ @(1).val, VRCDiag(fCompose(@(4).val.element, @(2).val), @(4).val.v) ]),

    # VGath * Diag
    CommuteVGathDiag := ARule( Compose,
       [ @(1, VGath), @(2, Diag) ], # o 1-> 2->
        e -> [ VDiag(fCompose(@2.val.element, fTensor(@1.val.func, fId(@1.val.v))), @1.val.v).attrs(@(2).val), @1.val ]),

    # VGath * VDiag
    CommuteVGathVDiag := ARule( Compose,
       [ @(1, VGath), @(2, VDiag) ], # o 1-> 2->
        e -> [ VDiag(fCompose(@2.val.element, fTensor(@1.val.func, fId(@1.val.v))), @1.val.v).attrs(@(2).val), @1.val ]),

    # Diag * VScat
    CommuteDiagVScat := ARule( Compose,
       [ @(1, Diag), @(2, VScat) ], # <-1 <-2 o
        e -> [ @2.val, VDiag(fCompose(@1.val.element, fTensor(@2.val.func, fId(@2.val.v))), @2.val.v).attrs(@(1).val) ]),

    # VDiag * VScat
    CommuteVDiagVScat := ARule( Compose,
       [ @(1, VDiag), @(2, VScat) ], # <-1 <-2 o
        e -> [ @2.val, VDiag(fCompose(@1.val.element, fTensor(@2.val.func, fId(@2.val.v))), @2.val.v).attrs(@(1).val) ]),

    # RCVDiag * VScat
    CommuteRCVDiagVScat := ARule( Compose,
       [ @(1, RCVDiag), @(2, VScat) ], # <-1 <-2 o
        e -> [ @2.val, RCVDiag(fCompose(@1.val.element, @2.val.func), @2.val.v).attrs(@(1).val) ]),

    # RCVDiagSplit * VScat
    CommuteRCVDiagSplitVScat := ARule( Compose,
       [ @(1, RCVDiagSplit), @(2, VScat) ], # <-1 <-2 o
        e -> [ @2.val, RCVDiagSplit(fCompose(@1.val.element, fTensor(@2.val.func, fId(2))), @2.val.v).attrs(@(1).val) ]),

    # VGath * VDiag_x_I
    CommuteVGathVDiag_x_I := ARule( Compose,
        [ @(1, VGath), @(2, VDiag_x_I) ], # o 1-> 2->
    e -> [ VDiag_x_I(fCompose(@2.val.element, @1.val.func), @2.val.v).attrs(@(2).val), @1.val ]),

    # VDiag_x_I * VScat
    CommuteVDiag_x_IVScat := ARule( Compose,
        [ @(1, VDiag_x_I), @(2, VScat) ], # <-1 <-2 o
    e -> [ @2.val, VDiag_x_I(fCompose(@1.val.element, @2.val.func), @1.val.v).attrs(@(1).val) ]),

    #------------------------
    # VGath_sv * Diag
    CommuteVGathsvDiag := ARule( Compose,
       [ @(1, VGath_sv), @(2, Diag) ], # o 1-> 2->
       e -> [ VDiag(_fComposeVGathScatSv_Diag(@1.val, Rows, @2.val), @1.val.v).attrs(@2.val), @1.val ]
    ),

    # VGath_sv * VDiag
    CommuteVGathsvVDiag := ARule( Compose,
       [ @(1, VGath_sv), @(2, VDiag) ], # o 1-> 2->
       e -> [ VDiag(_fComposeVGathScatSv_Diag(@1.val, Rows, @2.val), @1.val.v).attrs(@2.val), @1.val ]
    ),

    # Diag * VScat_sv
    CommuteDiagVScatsv := ARule( Compose,
       [ @(1, Diag), @(2, VScat_sv) ], # <-1 <-2 o
       e -> [ @2.val, VDiag(_fComposeVGathScatSv_Diag(@2.val, Cols, @1.val), @2.val.v).attrs(@1.val) ]
    ),

    # VDiag * VScat_sv
    CommuteVDiagVScatsv := ARule( Compose,
       [ @(1, VDiag), @(2, VScat_sv) ], # <-1 <-2 o
       e -> [ @2.val, VDiag(_fComposeVGathScatSv_Diag(@2.val, Cols, @1.val), @2.val.v).attrs(@1.val) ]
    ),

    #-----------------------------------------------------------
    # VStretchGath * Diag
    CommuteVStretchGathDiag := ARule( Compose,
       [ @(1, VStretchGath), @(2, Diag) ], # o 1-> 2->
       e -> let( vg := @1.val, den := vg.func.domain()/vg.part, num := _roundup(den, vg.v),
            [ VDiag(fStretch(fCompose(@2.val.element, @1.val.func), num, den), @1.val.v).attrs(@2.val), @1.val ])
    ),

    # Diag * VStretchScat
    CommuteVStretchScatDiag := ARule( Compose,
       [ @(1, Diag), @(2, VStretchGath)],
       e -> let( vg := @2.val, den := vg.func.domain()/vg.part, num := _roundup(den, vg.v),
            [ @2.val, VDiag(fStretch(fCompose(@1.val.element, @2.val.func), num, den), @2.val.v).attrs(@1.val) ])
    ),

    #-----------------------------------------------------------
    # Diag * IxVScat_pc
    CommuteDiagIxVScat_pc := ARule( Compose,
	[ @(1, [Diag, VDiag]), @(2, IxVScat_pc) ], # <-1 <-2 o
        e -> let(s:=@(2).val, [ s, VDiag(fStretch(@(1).val.element, _roundup(s.n, s.v), s.n), s.v) ])),

    # IxVGath_pc * Diag
    CommuteIxVGath_pcDiag := ARule( Compose,
	[ @(1, IxVGath_pc), @(2, [Diag, VDiag]) ], # o 1-> 2->
        e -> let(g:=@(1).val, [ VDiag(fStretch(@(2).val.element, _roundup(g.n, g.v), g.n), g.v), g ])),

    # Diag * VScat_pc
    CommuteDiagVScat_pc := ARule(Compose,
       [ @(1, [Diag, VDiag]), @(2, VScat_pc) ], # <-1 <-2 o
       e -> let(s := @(2).val, f := @(1).val.element,
	   [ s, VDiag(fStretch(fCompose(f, fAdd(f.domain(), s.n, s.ofs)),
			       _roundup(s.n, s.v), s.n), s.v) ])),

    # VGath_pc * Diag
    CommuteVGath_pcDiag := ARule( Compose, # o 1-> 2->
       [ @(1, VGath_pc), @(2, [Diag, VDiag]) ],
       e -> let(g := @(1).val, f := @(2).val.element,
	   [ VDiag(fStretch(fCompose(f, fAdd(f.domain(), g.n, g.ofs)),
		            _roundup(g.n, g.v), g.n), g.v), g ])),

    #-----------------------------------------------------------
    # YSV: Below 2 rules have limitations which can be removed,
    #      But it seems that fStretch has built-in assumptions which makes it difficult
    #      to remove these limitations..

    # NOTE: Remove limitations, the rule as is, is invalid for g.ofs <> 0, or if g.N > g.v
    #        Hence the precondition
    ## Diag * IxRCVScat_pc
    CommuteDiagIxRCVScat_pc := ARule(Compose,
       [ @(1, [Diag, VDiag]), @(2, IxRCVScat_pc, e->e.ofs = 0 and not IsSymbolic(e.n) and e.n <= e.v) ], # <-1 <-2 o
       e -> let(s := @(2).val,
	   [ s, VDiag(fStretch(@(1).val.element,
			       _roundup(s.n, 2*s.v), 2*s.n), s.v) ])),

    # NOTE: Remove limitations, the rule as is, is invalid for g.ofs <> 0, or if g.N > g.v
    #        Hence the precondition
    # IxRCVGath_pc * Diag
    CommuteIxRCVGath_pcDiag := ARule( Compose, # o 1-> 2->
       [ @(1, IxRCVGath_pc, e->e.ofs = 0 and not IsSymbolic(e.n) and e.n <= e.v), @(2, [Diag, VDiag]) ],
       e -> let(g := @(1).val,
	   [ VDiag(fStretch(@(2).val.element,
		            _roundup(g.n, 2*g.v), 2*g.n), g.v), g ])),
    #-----------------------------------------------------------

    #-----------------------------------------------------------
# NOTE: unclear how to do. Rewriting rule missing: f^{n->N} o \pi^{n} -> \rho^{N} o h^{n->N}
#    # BlockVPerm * VDiag
#    CommuteBlockVPermVDiag := ARule( Compose,
#       [ @(1, BlockVPerm), @(2, VDiag) ], # o 1-> 2->
#         e -> let(vdiag := Copy(@2.val), bperm := Copy(@1.val),
#         [ VDiag(fCompose(vdiag.element, fTensor(fId(bperm.n), bperm.perm)), vdiag.v).attrs(vdiag), BlockVPerm2(bperm.n, bperm._children[1], bperm.perm) ])),
#
#    # VDiag * BlockVPerm
#    CommuteVDiagBlockVPerm := ARule( Compose,
#       [ @(1, VDiag), @(2, BlockVPerm) ], # <-1 <-2 o
#         e -> let(vdiag := Copy(@1.val), bperm := Copy(@2.val),
#        [ BlockVPerm2(bperm.n, bperm._children[1], bperm.perm), VDiag(fCompose(vdiag.element, fTensor(fId(bperm.n), bperm.perm)), vdiag.v).attrs(vdiag) ])),

# this is a hack that *DOES NOT* produce correct code
#    # BlockVPerm * VDiag: HACK that *DOES NOT* produce correct code
#    CommuteBlockVPermVDiag := ARule( Compose,
#       [ @(1, BlockVPerm), @(2, VDiag) ], # o 1-> 2->
#         e -> let(vdiag := Copy(@2.val), bperm := Copy(@1.val),
#         [ vdiag, BlockVPerm2(bperm.n, bperm._children[1], bperm.perm) ])),
#
#    # VDiag * BlockVPerm: HACK that *DOES NOT* produce correct code
#    CommuteVDiagBlockVPerm := ARule( Compose,
#       [ @(1, VDiag), @(2, BlockVPerm) ], # <-1 <-2 o
#         e -> let(vdiag := Copy(@1.val), bperm := Copy(@2.val),
#        [ BlockVPerm2(bperm.n, bperm._children[1], bperm.perm), vdiag ])),

    #--------------------------------------------------
    #   vectorize constructs
    ParDiagVPerm := ARule(Compose, [@(1, Diag), @(2, VPerm)],
        e -> [VDiag(@1.val.element, @(2).val.vlen), @(2).val]
    ),
    ParVPermDiag := ARule(Compose, [@(1, VPerm), @(2, Diag)],
        e -> [@(1).val, VDiag(@2.val.element, @(1).val.vlen)]
    ),
    ParScatVDiag := ARule(Compose,
    [[Scat, [@(0,fTensor), ..., @(2,fId)]], @(1,VDiag,e->@(2).val.size=Rows(e))],
    e -> let(ch := @(0).val.children(), X := @(1).val, v := X.v,
               [VTensor(Scat(fTensor(DropLast(ch,1), fId(Rows(X)/v))), v), X])
    ),
    ParVTensorDiag := ARule(Compose, [@(1, VTensor), @(2, Diag)],
        e -> [ @(1).val, VDiag(@2.val.element, @(1).val.vlen)]
    ),
    ParDiagVTensor := ARule(Compose, [@(1, Diag), @(2, VTensor)],
        e -> [ VDiag(@1.val.element, @(2).val.vlen), @(2).val ]
    ),

    ParVSDiag := ARule(Compose, [@(1, VS), @(2, Diag)],
        e -> [ @(1).val, VDiag(@2.val.element, @(1).val.v)]
    ),

    ParDiagVS := ARule(Compose, [@(1, Diag), @(2, VS)],
        e -> [ VDiag(@1.val.element, @(2).val.v), @(2).val ]
    )
));

RewriteRules(RulesFuncSimp, rec(

    RCData_VDupOnline := Rule( [RCData, VDupOnline],
        e -> VDupOnline(RCData(e.func.func), e.func.v)),

    RCVData_VDupOnline := Rule( [RCVData, VDupOnline],
        e -> VDupOnline(RCVData(VDup(e.func.func, 1)), e.func.v)),

    RCVDataSplitVec_VDupOnline := Rule( [RCVDataSplitVec, VDupOnline],
        e -> VDupOnline(RCVDataSplitVec(VDup(e.func.func, 1)), e.func.v)),

    fCompose_VDupOnline_FList := ARule(fCompose, [@(1, VDupOnline), @(2, FList)],
        e -> [VDupOnline(fCompose(@(1).val.func, @(2).val), @(1).val.v)]),

    fPrecompute_VDupOnline := Rule( [fPrecompute, VDupOnline],
        e -> VDupOnline(fPrecompute(e.child(1).func), e.child(1).v)),

    RCVData_VDup_CRData := Rule([RCVData, [VDup, [CRData, @(1)], @(2)]],
        e -> VDup( VData(@(1).val, 2), @(2).val) ),
));


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

Class(MACROUnparser, ThreeOpMacroUnparser_Mixin, CMacroUnparserProg, rec(

    data   := (self, o, i, is) >> When(IsVecT(o.var.t),
                                     Print(Blanks(i), self.prefixT("VDECLC", o.var.t, [o.var] :: o.value.v), ";\n", self(o.cmd, i, is)),
                                     Inherited(o, i, is)),

    vpack  := (self, o, i, is) >> self.prefixT("VPK",  o.t, o.args),
    vdup   := (self, o, i, is) >> self.prefixT("VDUP", o.t, o.args[1]),

    assign_cmd := (self, o, i, is) >> Print(Blanks(i), self.(o.exp_op.__name__)(o, i, is), ";\n"),

    vunpacklo_2xf  := (self, o, i, is) >> self.prefix_T("VUNPKLO", o.args),
    vunpacklo_4xf  := ~.vunpacklo_2xf,
    vunpacklo_8xf  := ~.vunpacklo_2xf,

    vunpackhi_2xf  := (self, o, i, is) >> self.prefix_T("VUNPKHI", o.args),
    vunpackhi_4xf  := ~.vunpackhi_2xf,
    vunpackhi_8xf  := ~.vunpackhi_2xf,

    vunpacklo2_4xf := (self, o, i, is) >> self.prefix_T("VUNPKLO2", o.args),
    vunpacklo2_8xf := ~.vunpacklo2_4xf,

    vunpackhi_2xf  := (self, o, i, is) >> self.prefix_T("VUNPKHI", o.args),
    vunpackhi_4xf  := ~.vunpackhi_2xf,
    vunpackhi_8xf  := ~.vunpackhi_2xf,

    vunpackhi2_4xf := (self, o, i, is) >> self.prefix_T("VUNPKHI2", o.args),
    vunpackhi2_8xf := ~.vunpackhi2_4xf,

    vpacklo_2xf  := (self, o, i, is) >> self.prefix_T("VPKLO", o.args),
    vpacklo_4xf  := ~.vpacklo_2xf,
    vpacklo_8xf  := ~.vpacklo_2xf,

    vpackhi_2xf  := (self, o, i, is) >> self.prefix_T("VPKHI", o.args),
    vpackhi_4xf  := ~.vpackhi_2xf,
    vpackhi_8xf  := ~.vpackhi_2xf,

    vpacklo2_4xf := (self, o, i, is) >> self.prefix_T("VPKLO2", o.args),
    vpacklo2_8xf := ~.vpacklo2_4xf,

    vpackhi_2xf  := (self, o, i, is) >> self.prefix_T("VPKHI", o.args),
    vpackhi_4xf  := ~.vpackhi_2xf,
    vpackhi_8xf  := ~.vpackhi_2xf,

    vpackhi2_4xf := (self, o, i, is) >> self.prefix_T("VPKHI2", o.args),
    vpackhi2_8xf := ~.vpackhi2_4xf,

    vtrcx_8xf    := (self, o, i, is) >> self.prefix_T("VTRCX", o.args),

    vmulcx_2xf := (self, o, i, is) >> self.prefix_T("VMULCX", o.args),
    vmulcx_4xf := ~.vmulcx_2xf,
    vmulcx_8xf := ~.vmulcx_2xf,

    vswapcx_2xf := (self, o, i, is) >> self.prefix_T("VSWAPCX", o.args),
    vswapcx_4xf := ~.vswapcx_2xf,
    vswapcx_8xf := ~.vswapcx_2xf,

    fma  := (self, o, i, is) >> self.prefixTTT("FMA", o.args[1].t, o.args[2].t, o.args[3].t, o.args),
    fms  := (self, o, i, is) >> self.prefixTTT("FMS", o.args[1].t, o.args[2].t, o.args[3].t, o.args),
    nfma := (self, o, i, is) >> self.prefixTTT("NFMA", o.args[1].t, o.args[2].t, o.args[3].t, o.args),

    vstore_cmd := (self, o, i, is) >> Print(Blanks(i), self.prefixT("VSTORE", o.args[3].t, o.args), ";\n"),
    vload_cmd  := (self, o, i, is) >> Print(Blanks(i), self.prefixT("VLOAD", o.args[1].t, o.args), ";\n"),
));