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

FunctionEquality:=(a,b)-> (a=b) or (a.n=b.n and a.N=b.N and let(func1:=a.lambda(), func2:=b.lambda(),
	ForAll([1..a.N],e->func1.at(e).ev()=func2.at(e).ev())));

#######################################################
########  Redesigning the ruleset
#######################################################

Class(OLScatFuseRules, RuleSet);
RewriteRules(OLScatFuseRules, rec(
        Scat_ScatAcc := ARule(Compose,[@(1,[Scat, ScatAcc]),@(2,ScatAcc)],
            e->[ScatAcc(fCompose(@(1).val.func,@(2).val.func))]),

        ScatAcc_Scat := ARule(Compose,[@(1,ScatAcc),@(2,[Scat, ScatAcc])],
            e->[ScatAcc(fCompose(@(1).val.func,@(2).val.func))]),

        ICScatAcc_Scat := ARule(Compose,[@(1,ICScatAcc),@(2,[Scat, ICScatAcc])],
            e->[ICScatAcc(fCompose(@(1).val.func,@(2).val.func))]),

        Prm_ScatQuestionMark  := ARule(Compose, [ @(1, Prm), @(2, ScatQuestionMark) ],
            e -> [ ScatQuestionMark(fCompose(@(1).val.inverse, @(2).val.func)) ]),

        Prm_ICScatAcc := ARule(Compose, [ @(1, Prm), @(2, ICScatAcc) ],
            e -> [ ICScatAcc(fCompose(@(1).val.inverse, @(2).val.func)) ]),

        ScatQuestionMark_ScatQuestionMark :=
        ARule(Compose, [ @(1, ScatQuestionMark), @(2, ScatQuestionMark) ],
            e -> [ ScatQuestionMark(fCompose(@(1).val.func, @(2).val.func)) ]),

        Kill_NoPullId :=
        ARule(Compose,[[NoPull,[Prm,@,fId,fId]]],e->[]),

        Kill_ComposeIdL :=
        ARule(Compose,[@(1),[Prm,@,fId,fId]],e->[@(1).val]),

        Kill_ComposeIdR :=
        ARule(Compose,[[Prm,@,fId,fId],@(1).val],e->[@(1).val]),

#        Rule([@(1,Compose,e->Length(e.rChildren())>1),...,[Prm,@,fId,fId],...],e->Error("kill id")),

));

Class(OLVectorScatFuseRules, RuleSet);
RewriteRules(OLVectorScatFuseRules, rec(
        VScat_VConstruct :=
        ARule(Compose, [ @(1, VScat), @(2, [VScatAcc,VScatQuestionMark], e->@(1).val.v=e.v) ],
            e -> [ ObjId(@(2).val)(fCompose(@(1).val.func, @(2).val.func), @(1).val.v) ]),

        VScatQuestionMark_VConstruct :=
        ARule(Compose, [ @(1, VScatQuestionMark), @(2, [VScat,VScatAcc,VScatQuestionMark], e->@(1).val.v=e.v) ],
            e -> [ ObjId(@(1).val)(fCompose(@(1).val.func, @(2).val.func), @(1).val.v) ]),

        VScatAcc_VConstruct :=
        ARule(Compose, [ @(1, VScatAcc), @(2, [VScat,VScatAcc], e->@(1).val.v=e.v) ],
            e -> [ VScatAcc(fCompose(@(1).val.func, @(2).val.func), @(1).val.v) ]),

        ##The following rules are copies of the ones in the subvector system
        ScatQuestionMark_VConstruct := ARule(Compose, [ @(1, ScatQuestionMark), @(2, [VScat,VScatQuestionMark]) ],
            e -> [ VScat_svQuestionMark(@(1).val.func, getV(@(2).val), 1), @(2).val ]),

        ScatAcc_VConstruct := ARule(Compose, [ @(1, ScatAcc), @(2, [VScat,VScatAcc, VScatQuestionMark]) ],
            e -> [ VScat_svAcc(@(1).val.func, getV(@(2).val), 1), @(2).val ]),

    VScat_svQuestionMark__VScat := ARule(Compose, [@(1,[VScat_svQuestionMark,VScat_svAcc]), @(2,VScat)],
    e -> let(func := @(1).val.func,  vfunc := @(2).val.func,
             v    := getV(@(1).val), sv    := @(1).val.sv,
        [ ObjId(@(1).val)(fCompose(func, fTensor(vfunc, fId(v/sv))), v, sv) ])),

    GathScat_svQuestionMark_fTensor := Rule([@(1, [VGath_sv, VScat_svQuestionMark,VScat_svAcc]),
                            [@(2,fTensor), ..., [fId, @(3).cond(e -> Gcd(@(1).val.v/
@(1).val.sv, e) = @(1).val.v)]]],
     e -> let(v := @(1).val.v, sv := @(1).val.sv,
          n := @(3).val,   gcd := Gcd(v / sv, n),
          ObjId(@(1).val)(fTensor(DropLast(@(2).val.children(), 1), fId(n/gcd)), v, sv*gcd))),

    GathScat_sv_H := Rule([@(1, [VGath_sv, VScat_sv, VScat_svAcc]), [H, @(2),@(3).cond(e -> Gcd(@(1).val.v/
@(1).val.sv, e) = @(1).val.v),@(4),@(5)]],
            e->let(v := @(1).val.v, sv := @(1).val.sv,
          n := @(3).val,   gcd := Gcd(v / sv, n),
          mydiv:=When(IsSymbolic(@(4).val),idiv(@(4).val,gcd),@(4).val/gcd),
          ObjId(@(1).val)(H(@(2).val/gcd,n/gcd,mydiv,@(5).val), v, sv*gcd))),

        VScat_svQuestionMark__VScatQuestionMark := ARule(Compose, [@(1,[VScat_svQuestionMark,VScat_svAcc]), @(2,VScatQuestionMark)],
            e -> let(func := @(1).val.func,  vfunc := @(2).val.func,
                v    := getV(@(1).val), sv    := @(1).val.sv,
                [ ObjId(@(1).val)(fCompose(func, fTensor(vfunc, fId(v/sv))), v, sv) ])),

        VScat_svAcc__VScatAcc := ARule(Compose, [@(1,[VScat_svQuestionMark,VScat_svAcc]), @(2,VScatAcc)],
            e -> let(func := @(1).val.func,  vfunc := @(2).val.func,
                v    := getV(@(1).val), sv    := @(1).val.sv,
                [ VScat_svAcc(fCompose(func, fTensor(vfunc, fId(v/sv))), v, sv) ])),

        VScat_svQuestionMark_to_VScatQuestionMark :=
        Rule(@(1,VScat_svQuestionMark,e->getV(e)=e.sv), e->VScatQuestionMark(@(1).val.func, @(1).val.v)),

        VScat_svAcc_to_VScatAcc :=
        Rule(@(1,VScat_svAcc,e->getV(e)=e.sv), e->VScatAcc(@(1).val.func, @(1).val.v)),

));

Class(OLVectorPropagateRules, RuleSet);
RewriteRules(OLVectorPropagateRules, rec(
        VTensorOL_ScatAcc :=
        Rule([@(1,VTensor_OL),[@(2,Compose),@(3,ScatAcc),...]],
            e->VScatAcc(@(3).val.func,@(1).val.vlen)*
            VTensor_OL(Compose(Drop(@(2).val._children,1)),@(1).val.vlen)),

        VTensorOL_ScatQuestionMark :=
        Rule([@(1,VTensor_OL),[@(2,Compose),@(3,ScatQuestionMark),...]],
            e->VScatQuestionMark(@(3).val.func,@(1).val.vlen)*
            VTensor_OL(Compose(Drop(@(2).val._children,1)),@(1).val.vlen)),

        VTensorOL_Cross :=
        Rule([@(1,VTensor_OL),[@(2,Compose),...,@(3,Cross)]],
            e->VTensor_OL(Compose(DropLast(@2.val._children,1)),@(1).val.vlen)*
            Cross(List(@(3).val._children,t->VTensor(t,@(1).val.vlen)))),

        VTensorOL_ISum := Rule([@(1, VTensor_OL), @(2, [ISum])],
            e -> let(s := @(2).val, CopyFields(s,
                    rec(_children := List(s.children(), c->VTensor_OL(c, @(1).val.vlen)))))),

        VTensor_I :=
        Rule([@(1,VTensor),@(2,I)],
            e->VTensor(Prm(fId(@(2).val.dimensions[1])),@(1).val.vlen)),

));

OLVectorPropagateRuleset := MergedRuleSet(OLVectorPropagateRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLPushScatQuestionMarkInRules, RuleSet);
RewriteRules(OLPushScatQuestionMarkInRules, rec(
        ScatQuestionMark_NoPullRight := ARule(Compose, [@(1, [ScatQuestionMark,VScatQuestionMark]),@(2,NoPullRight)],
            e -> [ NoPullRight(@(1).val * @(2).val.rChildren()[1])]),

        ScatQuestionMark_Isum := ARule(Compose, [@(1, [ScatQuestionMark,VScatQuestionMark]), @(2,ISum)],
            e-> [ ISum(@(2).val.var,@(2).val.domain,Compose(@(1).val,@(2).val._children))]),
));

OLPushScatQuestionMarkInRuleset := MergedRuleSet(OLPushScatQuestionMarkInRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLAlreadyInitializedScatQuestionMarkRules, RuleSet);
RewriteRules(OLAlreadyInitializedScatQuestionMarkRules, rec(
        AlreadyInitializedScatQuestionMark := Rule(@(1, [ScatQuestionMark]),
            e-> ScatAcc(@(1).val.func)),

        AlreadyInitializedVScatQuestionMark := Rule(@(1, [VScatQuestionMark]),
            e-> VScatAcc(@(1).val.func,@(1).val.v)),
));

Class(OLScatQuestionMarkToScat, RuleSet);
RewriteRules(OLScatQuestionMarkToScat, rec(
        ScatQuestionMarkToScat := Rule(@(1, [ScatQuestionMark]),
            e-> Scat(@(1).val.func)),

        VScatQuestionMarkToScat := Rule(@(1, [VScatQuestionMark]),
            e-> VScat(@(1).val.func,@(1).val.v)),
));

Class(OLQuickAndDirtyHackForCodelet, RuleSet);
RewriteRules(OLQuickAndDirtyHackForCodelet, rec(
        ScatAcc_NoPullRight := ARule(Compose, [@(1, [ScatAcc,VScatAcc]),@(2,NoPullRight)],
            e -> [ NoPullRight(@(1).val * @(2).val.rChildren()[1])]),

        ScatAcc_Isum := ARule(Compose, [@(1, [ScatAcc, VScatAcc]), @(2,ISum)
],
            e-> [ ISum(@(2).val.var,@(2).val.domain,Compose(@(1).val,@(2).val._children))]),
));

OLAlreadyInitializedScatQuestionMarkRuleset := MergedRuleSet(OLAlreadyInitializedScatQuestionMarkRules,
    StandardSumsRules, OLScatFuseRules, OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLSplitScatQuestionMarkRules, RuleSet);
RewriteRules(OLSplitScatQuestionMarkRules, rec(

        SplitScatQuestionMark := Rule(@(1, [ScatQuestionMark]),
            e-> ScatInit(@(1).val.func , [],ScatAcc(@(1).val.func))),

        PullScatInitOutOfCompose := Rule([@(1,Compose),@(2,ScatInit),...],
            e-> ScatInit(@(2).val.func,@(2).val.cond,
                Compose(@(2).val._children,Drop(@(1).val._children,1)))),

        PullScatInitOutOfISum := Rule([@(1,ISum),@(2,ScatInit)],
            e->When(@(1).val.var in @(2).val.func.free(),
                ScatInitProbe(@(2).val.func,@(2).val.cond,
                    ISum(@(1).val.var,@(1).val.domain,
                        ScatInitFixed(@(2).val.func,@(2).val.cond,@(2).val._children))),
                ScatInit(@(2).val.func,@(2).val.cond,
                    ISum(@(1).val.var,@(1).val.domain,@(2).val._children)))),

        PullScatInitProbeOutOfCompose := Rule([@(1,Compose),@(2,ScatInitProbe),...],
            e-> ScatInitProbe(@(2).val.func,@(2).val.cond,
                Compose(@(2).val._children,Drop(@(1).val._children,1)))),

        PullScatInitProbeOutOfISum := Rule([@(1,ISum),@(2,ScatInitProbe)],
            e->When(@(1).val.var in @(2).val.func.free(),
                ScatInitProbe(@(2).val.func,@(2).val.cond,
                    ISum(@(1).val.var,@(1).val.domain,@(2).val._children)),
                ScatInitProbe(@(2).val.func,Concatenation([@(1).val.var],@(2).val.cond),
                    ISum(@(1).val.var,@(1).val.domain,@(2).val._children)))),

));
OLSplitScatQuestionMarkRuleset := MergedRuleSet(OLSplitScatQuestionMarkRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLScatProbeMergeRules, RuleSet);
RewriteRules(OLScatProbeMergeRules, rec(

        PushScatInitProbeInsideSum := Rule([@(1,ScatInitProbe),@(2,ISum),@],
            e->ISum(@(2).val.var,@(2).val.domain,
                ScatInitProbe(@(1).val.func,@(1).val.cond,Compose(@(2).val._children)))),

        PushScatInitProbeInsideCompose := Rule([@(1,ScatInitProbe),@(2,Compose),@],
            e-> Compose([ScatInitProbe(@(1).val.func,@(1).val.cond,@(2).val._children[1]),
                    Drop(@(2).val._children,1)])),

        ScatInit_Cross := ARule(Compose, [@(1,[ScatInit]), @(2, [Cross])],
            e-> [ ScatInit(@(1).val.func,@(1).val.cond,Compose(@(1).val._children,@(2).val))]),

        ScatInitProbe_ScatInitFixed := Rule([@(1,ScatInitProbe),@(2,ScatInitFixed),@],
            e->When(@(1).val.func=@(2).val.func,
                ScatInit(@(1).val.func,@(1).val.cond,@(2).val._children),
                Error("ScatInitProbe doesn t remerge with ScatInitFixed, how did that happen?"))),

        PushScatInitInsideCompose := Rule([@(1,ScatInit),@(2,Compose),@],
            e-> Compose([ScatInit(@(1).val.func,@(1).val.cond,@(2).val._children[1]),
                    Drop(@(2).val._children,1)])),

        ScatInit_ScatAcc :=
        Rule([@(1,ScatInit,e->ObjId(e._children)=ScatAcc and e.func=e._children.func and e.cond=[]),@(2,ScatAcc),@],
            e-> Scat(@(1).val.func)),
));

OLScatProbeMergeRuleset := MergedRuleSet(OLScatProbeMergeRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLScatAccPeelRules, RuleSet);
RewriteRules(OLScatAccPeelRules, rec(
    ScatInitPeel := Rule(
        [@(1,ScatInit, e->e.cond=[]),
            [@(2,ISum), [@(3,Compose),
                    @(4,ScatAcc,e->FunctionEquality(e.func,@(1).val.func)), ... ]],@],

	e-> let(v := @(2).val.var, newv := Ind(v.range-1),
#x            Buf(Scat(fId(@(1).val.dims()[1]))) *
            SUM(SubstVars(
                    Scat(@(4).val.func) * Compose(Drop(Copy(@(3).val.children()),1)),
                    rec((v.id) := V(0))),
                Cond(@(2).val.domain=2,
                    SubstVars(@(2).val.child(1),rec((v.id) := V(1))),
                    SubstVars(
                        ISum(newv, @(2).val.domain-1, @(2).val.child(1)),
                        rec((v.id) := newv+1)))
            )))
));

OLScatAccPeelRuleset := MergedRuleSet(OLScatAccPeelRules,StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLCrossPullInRules, RuleSet);
RewriteRules(OLCrossPullInRules, rec(
        PullInSMPSumRight := ARule( Compose, [ @(1, [Prm, Scat]), @(2, [SMPSum]) ],
            e -> [ CopyFields(@(2).val, rec(
             _children :=  List(@(2).val._children, c -> @(1).val * c),
             dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

        SMPSum_Cross  := ARule(Compose, [ @(1, [SMPSum]), @(2, [Cross]) ],
            e -> [ ObjId(@(1).val)(@(1).val.p,@(1).val.var, @(1).val.domain, @(1).val.child(1) * @(2).val) ]),

        ISum_Cross  := ARule(Compose, [ @(1, [ISum, ISumAcc]), @(2, [Cross]) ],
            e -> [ ObjId(@(1).val)(@(1).val.var, @(1).val.domain, @(1).val.child(1) * @(2).val) ]),

        SUM_Cross  := ARule(Compose, [ @(1, [SUM]), @(2, [Cross]) ],
            e -> [ ObjId(@(1).val)(List(@(1).val._children,x->x*@(2).val)) ]),

        ScatAcc_RecursStep := ARule(Compose, [ @(1, [ScatAcc]), @(2, [RecursStep]) ],
            e -> [ RecursStep(@2.val.yofs, @2.val.xofs,
                     @(1).val* @2.val.child(1)).attrs(@(1).val) ]),

        RecursStep_Cross := ARule(Compose, [ @(1, [RecursStep]), @(2, [Cross]) ],
            e -> [ RecursStep(@1.val.yofs, @1.val.xofs,
                    @1.val.child(1) * @(2).val).attrs(@(1).val) ]),

        Cross_Cross := ARule(Compose, [ @(1, [Cross, CrossBlockTop]), @(2, [Cross]) ],
            e -> [ ObjId(@(1).val)(@(1).val.child(1) * @(2).val.child(1),
                    @(1).val.child(2) * @(2).val.child(2)).attrs(@(1).val) ]),

        CrossBlockTopPass := Rule(@(1,CrossBlockTop,e->not((ObjId(e.rChildren()[2])=Prm) and
                ObjId(e.rChildren()[2].rChildren()[2])=fId and
                ObjId(e.rChildren()[2].rChildren()[3])=fId)),
        e->Cross(Prm(fId(@(1).val.rChildren()[1].dims()[1])),@(1).val.rChildren()[2])*
            CrossBlockTop(@(1).val.rChildren()[1],
                Prm(fId(@(1).val.rChildren()[2].dims()[2])))
        )
));

OLCrossPullInRuleset := MergedRuleSet(OLCrossPullInRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);

Class(OLAfterCrossPullInRuleset, RuleSet);
RewriteRules(OLAfterCrossPullInRuleset, rec(
        Cross_CompatibleISum  := Rule([Cross, @(1, ISum), @(2, ISum,e->e.domain=@(1).val.domain) ],
            function(e)
              local varmap, var;
              varmap:=tab();
              var:=@(2).val.var;
              varmap.(var.id):=@(1).val.var;
              return ISum(@(1).val.var, @(1).val.domain,
                  Cross(@(1).val.rChildren()[1],SubstVars(@(2).val.rChildren()[1],varmap)));
            end)
));

Class(OLTearCrossRules, RuleSet);
RewriteRules(OLTearCrossRules, rec(
#        FullTearVGath_dup:=ARule(Cross,[@(1,VGath_dup)],e->[VGath(@(1).val.func,@(1).val.v)*VGath_dup(fId(@(1).val.func.range()),@(1).val.v)]),

#HACK, of course
        HalfTearVGath_dup:=Rule([@(1,VGath_dup),[@(2,fTensor),@(3,fBase),fBase,...]],e->
            let(func:=fTensor(Drop(Copy(@(2).val.rChildren()),1)),
            VGath(func,@(1).val.v)*
            VGath_dup(fTensor(@(3).val,fId(func.range())),@(1).val.v))),

        TearCross:=ARule(Compose,[@(1,Cross,e->e.hasRightScat() and e.hasLeftGath())],
            e->[Copy(@(1).val.splitCross()[1]),Copy(@(1).val.splitCross()[2])]),

        PushCrossOutOfIsum :=
        Rule([@(1,ISum), @(2,Compose,e->ObjId(Last(e.rChildren()))=Cross and
                    Last(e.rChildren()).hasRightScat() and not Last(e.rChildren()).hasLeftGath())],
            e->Compose(ISum(@(1).val.var, @(1).val.domain,
                    Compose(DropLast(@(2).val.rChildren(),1))),Last(@(2).val.rChildren()))),

#        PushCrossOutOfSMPSum :=
#        Rule([@(1,SMPSum), @(2), @(3,Compose,e->ObjId(Last(e.rChildren()))=Cross and
#                    Last(e.rChildren()).hasRightScat() and not Last(e.rChildren()).hasLeftGath())],
#
#            e->Compose(SMPSum(@(2).val,@(1).val.var, @(1).val.domain,
#                    Compose(DropLast(@(3).val.rChildren(),1))),Last(@(3).val.rChildren()))),

        CrossConditionalMerge :=
        ARule(Compose, [ @(1, Cross), @(2, Cross, e->e.hasRightScat() and
                    @(1).val.hasRightScat() and not(@(1).val.hasLeftGath()) and not(e.hasLeftGath())) ],
            e -> [ ObjId(@(1).val)(@(1).val.child(1) * @(2).val.child(1),@(1).val.child(2) *
                    @(2).val.child(2)).attrs(@(1).val) ]),
));

OLTearCrossRuleset := MergedRuleSet(OLTearCrossRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec);

Class(OLSingleComposeRuleset, RuleSet);
RewriteRules(OLSingleComposeRuleset, rec(
        SingleCompose := Rule([@(1,Compose),@(2)],e->@(2).val),
));

Class(OLDropVGath_dupRuleset, RuleSet);
RewriteRules(OLDropVGath_dupRuleset, rec(
        DropVGath_dup := Rule(@(1,VGath_dup),e->VGath(@(1).val.func,@(1).val.v))
));

Class(OLDropSMPSumRuleset, RuleSet);
RewriteRules(OLDropSMPSumRuleset, rec(
        DropSMPSum := Rule(@(1,SMPSum),e->ISum(@(1).val.var,@(1).val.domain,@1.val.rChildren()[2]))
));

#########  HACCCK !!!
Class(OLMagicVUnrollDupRuleset, RuleSet);
RewriteRules(OLMagicVUnrollDupRuleset, rec(
#        MagicVGath_dup := Rule(@(1,VGath_dup,e->e.func.domain()>128 and e.func.domain() mod (32*4)=0),
#            e->let(itid:=Ind(4),idx:=Ind(@(1).val.func.domain()/(32*4)),
#                SMPSum(4,itid,4,
#                    ISum(idx,@(1).val.func.domain()/(32*4),
#                        VScat(fTensor(fBase(itid),fBase(idx),fId(32)), 2)*
#                        VGath_dup(fTensor(fBase(itid),fBase(idx),fId(32)), 2))),

        MagicVGath_dup := Rule(@(1,VGath_dup,e->e.func.domain()>32),
            e->@(1).val.toloop(32))
));

#Class(OLLiftBarrierRuleset, RuleSet);
#RewriteRules(OLLiftBarrierRuleset, rec(
#        EliminateBarrier := ARule(Compose, [@(1,RewriteBarrier),@(2)],
#            e->[@(2).val]),
#));

OLDefaultStrategy := [TerminateSymSPL,RulesTerm];

#################################################################
Class(OLRulesCode, RuleSet);
RewriteRules(OLRulesCode, rec(

        #Cross must not be BBized without neighbours if it has some identities
        CrossBB_in := Rule([BB,[@(1,Cross),...,[Prm,@,fId,fId],...]],
            e->Cross(List(@(1).val.rChildren(),x->BB(x)))
        ),

        DoubleBB := Rule([BB,@(1,BB)],e->@(1).val),

        #BB should go out if the inner are not Ids (simplified to perms in this case)
        Cross_BB_out := Rule([Cross, @(1, BB,e->not(ObjId(e.rChildren()[1])=Prm)), @(2, BB,e->not(ObjId(e.rChildren()[1])=Prm)) ],
            e -> BB(Cross(@(1).val.child(1),@(2).val.child(1)))),

        Drop_NoPull := Rule(@(1,NoPull),e->@(1).val.rChildren()[1]),

));

Class(OLRulesBufferFinalize, RuleSet);
RewriteRules(OLRulesBufferFinalize, rec(
    FinalizeNoPull := Rule(@(1, [NoPull, NoPullLeft, NoPullRight]), i->@1.val.child(1)),
    DropCrossBlockTop := Rule(@(1,CrossBlockTop), e->Cross(@1.val.rChildren()))
));

#RewriteRules(RulesStrengthReduce, rec(
# vdupmultsR:= Rule([mul, @(1), @(2).cond(e->ObjId(e.t)=TVect and ObjId(@(1).val.t)<>TVect)], e->mul(vdup(@(1).val, @(2).val.t.size), @(2).val)),
#
#vdupmultsL:= Rule([mul, @(1), @(2).cond(e->ObjId(@(1).val.t)=TVect and ObjId(e.t)<>TVect)], e->mul(@(1).val,vdup(@(2).val, @(1).val.t.size))),
#
#));

Class(OLPushScatAccRules,RuleSet);

#ScatAcc that are pushed inside sums MUST merge or else things get added twice or more...
RewriteRules(OLPushScatAccRules, rec(
        PushScatAccInsideISum := ARule(Compose, [@(1, ScatAcc), @(2,ISum)],
            e-> [ ISum(@(2).val.var,@(2).val.domain,Compose(@(1).val,@(2).val._children))]),

        PushScatAccInsideSUM := ARule(Compose, [@(1, ScatAcc), @(2,SUM)],
            e-> let(childs:=List(@(2).val._children,e->@(1).val*e), [ SUM(childs)])),

        PushICScatAccInsideISum := ARule(Compose, [@(1, ICScatAcc), @(2,ISum)],
            e-> [ ISum(@(2).val.var,@(2).val.domain,Compose(@(1).val,@(2).val._children))]),

        PushICScatAccInsideSUM := ARule(Compose, [@(1, ICScatAcc), @(2,SUM)],
            e-> let(childs:=List(@(2).val._children,e->@(1).val*e), [ SUM(childs)])),

));
OLPushScatAccRuleset := MergedRuleSet(OLPushScatAccRules, StandardSumsRules, OLScatFuseRules,
    OLVectorScatFuseRules, RulesVec, RulesPropagate);


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

#expand FData to report range as size
Class(FDataIndirect,FData,rec (
    range := self >> self.var.t.size,
));

Class(ICFDataIndirect,FDataIndirect,rec (
    range := self >> 2*self.var.t.size,
    domain := self >> 2*self.var.t.size,
));

Class(ExpandMult,NonTerminal,rec (
        abbrevs := [(height,rows,isSplit) -> [height,rows,isSplit,[]]],
        dmn := self >> let(x:=When(self.params[3],2,1),[TArray(TReal,x*self.params[1] * self.params[2]),TArray(TReal,x*self.params[2])]),
        rng := self >> let(x:=When(self.params[3],2,1),[TArray(TReal,x*self.params[1]* self.params[2])]),
        isReal := self >> true,
        tagpos := 4,
        transpose := self >> Copy(self)));

NewRulesFor(ExpandMult,rec(

       ExpandMult_Base:= rec (
        forTransposition := false,
        applicable := (self,nt) >> nt.params[1]=1,
        apply := (nt,c,cnt) -> When(nt.params[3],ICMultiplication(2,2*nt.params[2],true),ICMultiplication(2,nt.params[2]))),

       ExpandMult_One := rec (
        forTransposition := false,
        applicable := (self,nt) >> nt.params[1] > 1,
        freedoms := nt -> [[1]],
        child := (self,nt,freedoms) >> [TTensorI_OL(ExpandMult(1,nt.params[2],nt.params[3]),[APar,AOne],[[1,0]],
                        [nt.params[1]/freedoms[1],1])],
        apply := (nt,c,cnt) -> c[1]
       )));

Class(SAR_Interpolation,NonTerminal,rec(
        abbrevs := [(iters,height,rows,isSplit) -> Checked(IsPosInt(rows) and IsPosInt(iters) and
                     IsPosInt(height),[iters,height,rows,isSplit,[]]),
                    (iters,height,rows,isSplit,t) -> Checked(IsPosInt(rows) and IsPosInt(iters) and
                        IsPosInt(height), [iters,height,rows,isSplit,t])],
        dmn := self >> [TArray(TReal,2*self.params[1] * self.params[2] * self.params[3]),
                        TArray(TReal,2*self.params[1] * self.params[3])],
        rng := self >> [TArray(TReal,2*self.params[1] * self.params[3])],
        isReal := self >> true,
        isSplit := self >> self.params[4],
        numops := self >> self.params[1] * self.params[2] * self.params[3] * self.params[1],
        tagpos := 5,
        transpose := self >> Copy(self)));

NewRulesFor(SAR_Interpolation,rec(

     SAR_Interpolation_One := rec (
        forTransposition := false,
        applicable := (self,nt) >> nt.params[1] > 1 and nt.params[2] > 1,
        freedoms := nt -> [[1]],
        child := function(nt,freedoms)
		local size,tt,fd;
                size:=(nt.params[1]*nt.params[2]*nt.params[3]);
		fd:=When(nt.params[4],
			ICFDataIndirect(param(TArray(TDouble,size),"indirect")),FDataIndirect(param(TArray(TDouble,size),"indirect")));
                tt:=When(nt.params[4],ExpandMult(nt.params[2],nt.params[3],true),
                        ExpandMult(nt.params[2],nt.params[3],false));
                nt.params[5] := When(nt.params[4],nt.params[1],nt.params[2]);
		return [ICScatAcc(fPrecompute(fd))*
			   TTensorI_OL(tt,[APar,AMul(1,APar)],[[1,0]],
                        [nt.params[1]/freedoms[1],nt.params[5]/freedoms[1]])];
                end,
        apply := (nt,c,cnt) ->  let(len1:=2*nt.params[1]*nt.params[2]*nt.params[3],
			            len2:=2*nt.params[1]*nt.params[3],
						When(nt.isSplit(),Split(len1,2)*Prm(L(len1,2))*c[1]*
                                			Cross(Prm(L(len1,len1/2)),Prm(L(len2,len2/2)))*
                                			Cross(Glue(2,len1/2),Glue(2,len2/2)),c[1])),

     )
   )
);

Class(SAR2,TaggedNonTerminal,rec(
       abbrevs := [(n,s) -> Checked(IsPosInt(n),[n,s])],
       dmn := self >> [TArray(TReal,When(self.params[1]=1,2,self.params[1])),TArray(TReal,When(self.params[1]=1,2,self.params[1]))],
       rng := self >> [TArray(TReal,When(self.params[1]=1,2,self.params[1]))],
       isSplit := self >> self.params[2],
       isReal := self >> true,
       transpose := self >> Copy(self)));

NewRulesFor(SAR2,rec(

      SAR_Base := rec (
      	forTransposition := false,
      	applicable := (self,nt) >> (nt.params[1]=1),
      	apply := (nt,c,cnt) -> ICMultiplication(2,2)),

      SAR_MatchFilter := rec (
	forTransposition := false,
        applicable := (self,nt) >> nt.params[1] >= 2,
        freedoms := nt -> [[2]],
        child := (self,nt,freedoms) >> [TTensorI_OL(SAR2(1,nt.params[2]),[APar,AMul(1,APar)],[[1,0]],
                        [nt.params[1]/freedoms[1],nt.params[1]/freedoms[1]])],
        apply := (nt,c,cnt) -> When(nt.isSplit(),Split(nt.params[1],2)*Prm(L(nt.params[1],2))*c[1]*
				Cross(Prm(L(nt.params[1],nt.params[1]/2)),Prm(L(nt.params[1],nt.params[1]/2)))*
				Cross(Glue(2,nt.params[1]/2),Glue(2,nt.params[1]/2)),c[1]),

      ),

      SAR_MatchFilterSMP := rec (
      	forTransposition := false,
 	#applicable := (self,nt) >> nt.params[1] >= 1 and nt.hasTags() and nt.isTag(1,AParSMP),
        applicable := (self,nt) >>  false,
        freedoms := nt -> Cond(nt.isTag(1, AParSMP) and
               nt.params[1] mod nt.firstTag().params[1]=0,
               [[nt.params[1]/nt.firstTag().params[1]]],
               [[1]]),
        child := (self,nt,freedoms) >> [TTensorI_OL(SAR2(freedoms[1],nt.params[2]),[APar,AMul(1,APar)],[[1,0]],
			[nt.params[1]/freedoms[1],nt.params[1]/freedoms[1]]).withTags(nt.getTags())],
        apply := (nt,c,cnt) -> c[1]
     ),

     SAR_MatchFilterVec := rec (
         forTransposition := false,
         #applicable := (self,nt) >> nt.hasTags() and nt.isTag(1,AVecReg) and nt.params[1] > nt.firstTag().v,
         applicable := (self,nt) >> false,
         freedoms := nt -> Cond(nt.isTag(1,AVecReg) and Mod(nt.params[1],nt.firstTag().v) = 0,
			[[nt.firstTag().v]],[[1]]),
         child := (self,nt,freedoms) >> [TTensorI_OL(SAR2(nt.firstTag().v,nt.firstTag()),[AVec,AMul(1,AVec)],[[0,1]],
			 [nt.params[1]/freedoms[1],nt.params[1]/freedoms[1]]).withTags(nt.getTags())],
         apply := (nt,c,cnt) -> c[1]
     ),

    SAR_MatchFilterVecBase := rec (
        forTransposition := false,
        #applicable := (self,nt) >> nt.hasTags() and nt.isTag(1,AVecReg) and nt.params[1] = nt.firstTag().v,
        applicable := (self,nt) >> false,
        apply := (nt,c,cnt) ->
                        VTensor_OL(Multiplication(2,1),nt.firstTag().v)

        )

    )

);


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

# idea : Restrict by dims(@(2)) <= codelet size
#
RewriteRules(RulesSums, rec(
    CommutePrmScatInplace := ARule(Compose, [@(1, [Prm,Scat]), [Inplace, @(2)]],
	e -> [ Inplace(@(1).val * @(2).val * @(1).val.transpose()), Copy(@(1).val) ]),

    CommuteInplaceGath := ARule(Compose, [[Inplace, @(2)], @(1, Gath)],
	e -> [ Copy(@(1).val), Inplace(@(1).val.transpose() * @(2).val * @(1).val) ])
));

Class(RulesInplace, RuleSet);
RewriteRules(RulesInplace, rec(
    CommutePrmScatInplace := RulesSums.rules.CommutePrmScatInplace,
    CommuteInplaceGath := RulesSums.rules.CommuteInplaceGath,

    InplaceSUM := Rule([Inplace, @(1,SUM)], e -> SUM(List(@(1).val.children(), Inplace)))
));


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

runDramSysGen2D := function(k,n,sw,prec)
	local opts,t,rt,srt,path,cmd;

	opts := DRAMGlobals.getOpts(k*k, k*n, n, prec*2*sw, prec, 27, 0, 0);
	# restrict the TTensor rule to match the verification script assumptions
	opts.breakdownRules.TTensor := [AxI_L__BxI_L];
	t := MDDFT([n,n]).withTags(opts.tags);
	rt := RandomRuleTree(t, opts);
	srt := SPLRuleTree(rt);
	path := DRAMSystemGen(srt,opts);

	PrintLine(path);

	return path;
	#
	# cmd := ConcatenationString("mkdir /afs/ece.cmu.edu/project/trac/vol3/users/bakin/dramSysGen/",
	# 		"2D-n-",String(n),"-k-",String(k),"-sw-",String(sw),"-prec-",String(prec));
	#
	# Exec(cmd);
	#
	# cmd := ConcatenationString("mv ",path,"*"," /afs/ece.cmu.edu/project/trac/vol3/users/bakin/dramSysGen/",
	# 		"2D-n-",String(n),"-k-",String(k),"-sw-",String(sw),"-prec-",String(prec),"/");
	#
	# Exec(cmd);
end;

#runDramSysGen1D := function(k,n,sw,prec)
#	local opts,t,rt,srt,path,cmd;
#
#	opts := DRAMGlobals.getOpts(k*k, k*n, n, prec*2*sw, prec, 27);
#	t := DFT(n*n).withTags(opts.tags);
#	rt := RandomRuleTree(t, opts);
#	srt := SPLRuleTree(rt);
#	path := DRAMSystemGen(srt,opts);
#
#	# cmd := ConcatenationString("mkdir /afs/ece.cmu.edu/project/trac/vol3/users/bakin/dramSysGen/",
#	# 		"1D-n-",String(n),"-k-",String(k),"-sw-",String(sw),"-prec-",String(prec));
#	#
#	# Exec(cmd);
#	#
#	# cmd := ConcatenationString("mv ",path,"*"," /afs/ece.cmu.edu/project/trac/vol3/users/bakin/dramSysGen/",
#	# 		"1D-n-",String(n),"-k-",String(k),"-sw-",String(sw),"-prec-",String(prec),"/");
#	#
#	# Exec(cmd);
#end;
#
#runDramSysGen3D := function(k,n,sw,prec)
#	local opts,t,rt,srt,path,cmd;
#
#	opts := DRAMGlobals.getOpts(k*k*k, k*k*n, n, prec*2*sw, prec, 27);
#	t := MDDFT([n,n,n]).withTags(opts.tags);
#	rt := RandomRuleTree(t, opts);
#	srt := SPLRuleTree(rt);
#	path := DRAMSystemGen(srt,opts);
#
#	# cmd := ConcatenationString("mkdir /afs/ece.cmu.edu/project/trac/vol3/users/bakin/dramSysGen/",
#	# 		"3D-n-",String(n),"-k-",String(k),"-sw-",String(sw),"-prec-",String(prec));
#	#
#	# Exec(cmd);
#	#
#	# cmd := ConcatenationString("mv ",path,"*"," /afs/ece.cmu.edu/project/trac/vol3/users/bakin/dramSysGen/",
#	# 		"3D-n-",String(n),"-k-",String(k),"-sw-",String(sw),"-prec-",String(prec),"/");
#	#
#	# Exec(cmd);
#end;
#
#
#
#runDramSysGenAll := function()
#	local k,n,sw,prec;
#
#	k := 8;		n := 128;	sw := 2;	prec := 32;
#	runDramSysGen2D(k,n,sw,prec);
#	runDramSysGen1D(k,n,sw,prec);
#	k := 8;		n := 128;	sw := 4;	prec := 32;
#	runDramSysGen2D(k,n,sw,prec);
#	runDramSysGen1D(k,n,sw,prec);
#	k := 16;		n := 512;	sw := 2;	prec := 32;
#	runDramSysGen2D(k,n,sw,prec);
#	runDramSysGen1D(k,n,sw,prec);
#	k := 16;		n := 512;	sw := 4;	prec := 32;
#	runDramSysGen2D(k,n,sw,prec);
#	runDramSysGen1D(k,n,sw,prec);
#	k := 16;		n := 512;	sw := 8;	prec := 32;
#	runDramSysGen2D(k,n,sw,prec);
#	runDramSysGen1D(k,n,sw,prec);
#
#end;

runDramSysGenAll := function(k,n,sw,prec)
	local path,cmd;

	path := runDramSysGen2D(k,n,sw,prec);
	cmd := ConcatenationString("cd ",path,"; source verify.sh; grep SNR matlab.log > /afs/ece.cmu.edu/project/trac/vol3/users/bakin/perfect/snr-summary/snr.n",String(n),
								".k",String(k),".s",String(sw),".p",String(prec));
	Exec(cmd);

end;

runDramSysGenTest := function()
#	runDramSysGenAll(4,128,2,32);
#	runDramSysGenAll(8,128,2,32);
#	runDramSysGenAll(4,128,4,32);

#	runDramSysGenAll(4,256,2,32);
#	runDramSysGenAll(8,256,4,32);
#	runDramSysGenAll(16,256,4,32);

#	runDramSysGenAll(16,512,8,32);

	runDramSysGenAll(32,1024,4,32);

	runDramSysGenAll(16,2048,4,32);

end;


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

@_RecursBase := NewMakeRule(
    rec(applicable       := nt -> true,
	forTransposition := false,
	children         := nt -> [[ ]],
	apply := (nt,ch,nonterms) -> RecursStep(nt)),
        "@_RecursBase", @);

#F CreateRecursBaseHash(<smallHash>)
#F
#F Returns new hash table with T->@_RecursBase(T) ruletrees for each T in <smallHash>
#F
CreateRecursBaseHash := function(smallHash)
    local e, f, t, basehash;
    basehash := HashTableDP();
    for e in smallHash.entries do
	if e<>[] then
            for f in e do
              if f.data<>[] then
                  HashAdd(basehash, f.key, When(IsBound(f.data[1].measuredGP),[ rec(ruletree := @_RecursBase(f.key),measured := f.data[1].measuredGP) ],[ rec(ruletree := @_RecursBase(f.key))]));
              fi;
            od;
	fi;
    od;
    return basehash;
end;

Class(Codelet, BaseContainer);

_lookupFormula := function(smallHash, transform, opts)
    local lkup;
    lkup := HashLookup(smallHash, transform);
    if lkup = false
	then Error("Transform ", transform, " missing in opts.libgen.baseBench");
    else
	if not IsBound(lkup[1].sums) then
	    VPrintLine(opts.verbosity, "Sigma-splizing ", lkup[1].ruletree.node, "...");
	    # NOTE: generateComplexCode
	    lkup[1].sums := SumsRuleTree(lkup[1].ruletree, CopyFields(opts, rec(generateComplexCode := true)));
	fi;
	return Copy(lkup[1].sums);
    fi;
end;

Class(RecursStepCall, BaseOperation, rec(
    new := (self, dimensions, func, bindings) >> SPL(WithBases(self,
        rec(dimensions := dimensions,  func := func, bindings := bindings))),

    dims := self >> self.dimensions,
    rChildren := self >> [self.dimensions, self.func, self.bindings],
    rSetChild := rSetChildFields("dimensions", "func", "bindings"),
    numops := self >> 10^300,

    print := (self, i, is) >> Print(
        self.name, "(", self.dimensions, ", \"", self.func, "\", ", self.bindings, ")")
));

Class(RecursStepTerm, RuleSet);
RewriteRules(RecursStepTerm, rec(

    RecursStepToCall := Rule(RecursStep,
        meth(e, cx)
            local rs, opts, bindings, func, shape, lkup, params, clrec, smallhash, preimpl;
            opts := cx.opts;
            e := e.child(1);
	    e := SubstTopDown(e, BB, x->x.child(1));
            shape := CodeletShape(e);
            func := CodeletName(shape);
            rs := MkCodelet(e);
            params := CodeletParams(rs);
            bindings := Zip2(params, CodeletParams(e));

            lkup := HashLookup(opts.libgen.codeletTab, shape);
            smallhash := opts.libgen.baseBench.exp.bases.hashTable;
#should it be NoPull(_lookupFormula(...))??
            if lkup = false then
                preimpl := Copy(rs);
                rs := SubstBottomUpRules(rs, [
                    [RTWrap, r -> _lookupFormula(smallhash, r.rt.node, opts) ],
                    [@.cond(IsNonTerminal), r -> _lookupFormula(smallhash, r, opts) ]]);
                rs := ApplyStrategy(rs, opts.libgen.terminateStrategy, UntilDone, opts);
                clrec := rec(name := func, shape := shape, sums := rs, preimpl := preimpl, params := params);
                HashAdd(opts.libgen.codeletTab, shape, clrec);
                HashAdd(opts.libgen.codeletTab, func, clrec);
            fi;
            return RecursStepCall(rs.dimensions, func, bindings);
        end)
));

CreateCodeletHashTable := () ->
    HashTable((val,size)->InternalHash(CodeletName(val)));

CompileCodelets := function(c, opts)
    local calls, call, clet_code, lkup, res, funcs,outputs,inputs, u, t;
    calls := Collect(c, RecursStepCall);
    res := []; funcs := Set([]);
    for call in calls do
        if not call.func in funcs then
	    Add(funcs, call.func);
	    lkup := HashLookup(opts.libgen.codeletTab, call.func);
	    if lkup = false then Error(call.func, " is missing from codelet hash. ",
                                                  "Run SumsRuleTree again?"); fi;
	    if not IsBound(lkup.code) then
		VPrintLine(opts.verbosity, "Compiling ", call.func, "...");
                t := TPtr(Cond(opts.generateComplexCode, TComplex, TReal));
                outputs:=StripList(List([1..Length(Flat([lkup.sums.dims()[1]]))],x->var(Concat("Y",When(x>1,String(x),"")), t)));
                inputs:=StripList(List([1..Length(Flat([lkup.sums.dims()[2]]))],x->var(Concat("X",When(x>1,String(x),"")), t)));
                u:=opts.libgen.basesUnrolling;
                if IsBound(lkup.unrolling) then
                    u:=lkup.unrolling;
                fi;
		lkup.code := opts.codegen(Codelet(lkup.sums), outputs, inputs,
		    CopyFields(opts, rec(globalUnrolling := u)));
	    fi;
            #if Collect(lkup.code, errExp) <> [] then
            #    lkup.badcode := lkup.code; Unbind(lkup.code); Error("errExp"); fi;
	    Add(res, lkup);
	fi;
    od;
    return res;
end;


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

#######################################################################################
# Pointers
#
vvref := (loc, idx, vlen) -> nth(tcast(TPtr(TVect(loc.t.t, vlen)), loc), idx);
vtref := (vtype, loc, idx) -> nth(tcast(TPtr(vtype), loc), idx);

#######################################################################################
#   Shuffle instructions and instruction sets

Class(assign_sv, assign, rec(
   __call__ := (self, loc, exp, p) >> WithBases(self,
       rec(operations := CmdOps,
       loc := toAssignTarget(loc),
       exp := toExpArg(exp),
       p := p)),
    print := (self,i,is) >> Chain(Print(Blanks(i), self.name, "("), self.loc.print(), Print(", "), self.exp.print(), Print(", "), Print(self.p), Print(");\n")),
    getNoScalar := self >> [self.loc.loc]
));

_vcprintcs := function ( lst )
    local  i;
    if Length(lst) = 0  then
        return;
    fi;
    lst[1].vcprint();
    for i  in [ 2 .. Length(lst) ]  do
        Print(", ", lst[i].vcprint());
    od;
end;

# vec_shr(<a>, <n>)
#   shift vector <a> elements "right" ("left" in spiral - toward first vector element)
#   by <n> items, shifting in zeros.

Class(vec_shr, AutoFoldExp, rec(
    ev := self >> Error("make ev method"),
    computeType := self >> self.args[1].t
));

# vec_shl(<a>, <n>)
#   shift vector <a> elements "left" ("right" in spiral - toward last vector element)
#   by <n> items, shifting in zeros.

Class(vec_shl,  Exp, rec(
    ev := self >> Error("make ev method"),
    computeType := self >> self.args[1].t
));

# alignr(<x0>, <x1>, <shift>)
#   takes two vectors <x0> and <x1>, concatenates their values, and pulls out a
#   vector-length section from an offset given by <shift>.

Class(alignr,  Exp, rec(
    ev := self >> let(
        x := self.args[1].ev() :: self.args[2].ev(),
        s := self.args[3].ev(),
        x{[s..s+self.t.size-1]}
    ),
    computeType := self >> UnifyPair(self.args[1].t, self.args[2].t),
));


# Copyright 2018-2019, Carnegie Mellon University
# See LICENSE for details

Class(HCOLCodegen, DefaultCodegen, rec(
    PointWise := (self, o, y, x, opts) >> let(i := Ind(o.N),
        loop(i, o.N, assign(nth(y, i), o.op.at(nth(x, i), i)))),

    ISumReduction := (self, o, y, x, opts) >> let(op := o.op, idval := o.idval, c:=o.child(1),
        n := Rows(c), t := TempVec(TArray(y.t.base_t(), n)), i := Ind(n), j := Ind(n),
        chain(
            loop(j, n,
                assign(nth(y,j), idval)),

            Cond(o.isSaturated = False,
                loop(o.var, o.domain, decl([t],
                    chain(
                    self(o.child(1), t, x, opts),
                    loop(i, n,
                          assign(nth(y,i), op(nth(y,i), nth(t,i))))
                ))),
                loopc(o.var, o.domain, ApplyFunc(logic_and, List([0..n-1], e->logic_neg(o.isSaturated(nth(y,e))))), decl([t],
                    chain(
                    self(o.child(1), t, x, opts),
                    loop(i, n,
                          assign(nth(y,i), op(nth(y,i), nth(t,i))))
                )))
            )
        )),

	COND := (self, o, y, x, opts) >> let(
			#Print("Cond:",o.cond, "\nTrue: ", o.child(1), "\nFalse:", o.child(2),"\n"),
			t := TempVec(TArray(TBool,1)),
			c1:= o.child(1), c2 := o.child(2),

			decl([t],
				chain(
					self(o.cond, t, x, opts),
					IF( nth(t, 0),
						self(o.child(1), y, x, opts),
						self(o.child(2), y, x, opts))))),

    Constant := (self, o, y, x, opts) >> assign(y, o.value),

    eT := (self, o, y, x, opts) >> assign(nth(y,0), nth(x, o.base)),

    eUnion := (self, o, y, x, opts) >> When(o.N=0 and o.base=-1, chain(), assign(nth(y, o.base), nth(x,0)) ),

    Mul1 := (self, o, y, x, opts) >> assign(nth(y,0), o.element*nth(x, 0)),

    SUMUnion := (self, o, y, x, opts) >> chain(List(o.children(), c -> self(c, y, x, opts))),

    ISumUnion := (self, o, y, x, opts) >>
       loop(o.var, o.domain, self(o.child(1), y, x, opts)),

    Inductor := (self, o, y, x, opts) >> assign(nth(y,0), cond(eq(o.i,0), o.initval, o.op.at(nth(y,0), nth(x,0)))),

    BinOp := (self, o, y, x, opts) >> let(i := Ind(o.N),
        loop(i, V(o.N), assign(nth(y,i), o.op.at(nth(x, i), nth(x,i+o.N))))),

    OLCompose := meth(self, o,y,x,opts)
        local ch, numch, vecs;
        ch    := o.children();
        numch := Length(ch);

        # propagate x and y arrays and create temporary arrays
        vecs  := self._composePropagate(ch, y, x, c -> TempArray(y,x,c));

        # order them so that first to be evaluated is first in array.
        vecs := Reversed(vecs);
        ch   := Reversed(ch);

        # Wrap code in variable declaration. Each entry in vecs will contain multiple
        # arrays in the case of multi-input/output (i.e. OL)
        return decl(
            Difference(Flat(vecs{[2..Length(vecs)-1]}), Flat([x,y])),
            chain(
                List([1..numch], i ->
                     self(ch[i], vecs[i+1], vecs[i], opts)))
        );
    end,

));


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

Class(ScratchWrap, rec(
    __call__ := (self, size, nsgmts, linesize) >> Checked(
        Is2Power(size), Is2Power(nsgmts), Is2Power(linesize),

        WithBases(self, rec(
            operations := PrintOps,
            size := size,
            nsgmts := nsgmts,
            linesize := linesize
        ))
    ),

    wrap := (self, r, t, opts) >> let(
        tags := [ALStore(self.size, 1, self.linesize)]
            :: When(IsBound(r.node) and IsBound(r.node.getTags), r.node.getTags(), []),
        tt := GT(r.node, GTPar, GTPar, [1]).withTags(tags),
        rr := AllApplicableRules(tt, opts.breakdownRules),

        Checked(rr <> [],
            rr[1](tt, r))
    ),

    twrap := (self, t, opts) >> let(
        tags := [ALStore(self.size, 1, self.linesize)]
            :: When(IsBound(t._children) and IsBound(t.child(1).getTags), t.child(1).getTags(), []),
        tt := TTensorI(t, self.nsgmts, APar, APar).withTags(tags),

        tt
    ),

    print := (self) >> Print(self.name, "(", self.size, ", ", self.nsgmts, ", ", self.linesize, ")"),
));


InstallGlobalFunction(MitM_ATPToRec,
function(atp)
    local i, res;

    res := rec();

    for i in [1,3..Length(MitM_Content(atp))-1] do
        res.( MitM_Name(MitM_Content(atp)[i]) ) :=
            EvalString(MitM_OMRecToGAPNC(MitM_Content(atp)[i+1]).result);
    od;
    return res;
end);

InstallGlobalFunction(MitM_RecToATP,
function(r)
    return Objectify(MitM_OMRecType,
                     rec( name := "OMATP"
                        , content := Concatenation(
                                List( NamesOfComponents(r)
                                    , n -> [ OMS("scscp1", n)
                                           , MitM_GAPToOMRec(r.(n)) ] ) ) ));
end);

InstallGlobalFunction(OMATP,
function(r)
    return Objectify(MitM_OMRecType,
                     rec( name := "OMATP"
                        , content := Concatenation(
                                List( NamesOfComponents(r)
                                    , n -> [ OMS("scscp1", n)
                                           , r.(n) ] ) ) ));
end);

InstallGlobalFunction(OMOBJ,
function(obj)
    return Objectify( MitM_OMRecType
                    , rec( name := "OMOBJ"
                         , attributes := rec( version := "2.0" )
                         , content := [obj] ) );
end);

InstallGlobalFunction(OMA,
function(oms, args...)
    return Objectify( MitM_OMRecType
                    , rec( name := "OMA"
                         , content := Concatenation( [ oms ], args ) ) );
end);

InstallGlobalFunction(OMS,
function(args...)
    local res;

    res := rec( name := "OMS"
              , attributes := rec( ) );

    if Length(args) = 2 then
        res.attributes.cd := args[1];
        res.attributes.name := args[2];
    elif Length(args) = 3 then
        res.attributes.cdbase := args[1];
        res.attributes.cd := args[2];
        res.attributes.name := args[3];
    else
        return fail;
    fi;
    return Objectify(MitM_OMRecType, res);
end);

InstallGlobalFunction(MitM_SimpleOMS,
obj_name -> OMS(MitM_cdbase, "lib", obj_name));

InstallGlobalFunction(OMATTR,
function(attr, content)
    return Objectify( MitM_OMRecType
                    , rec( name := "OMATTR"
                         , content := [ MitM_RecToATP(attr)
                                      , content ] ) );
end);

InstallGlobalFunction(OMSTR,
function(string)
    if IsEmptyString(string) then
        return Objectify( MitM_OMRecType
                        , rec( name := "OMSTR"
                             , content := [ ] ) );
    else
        return Objectify( MitM_OMRecType
                        , rec( name := "OMSTR"
                             , content := [ string ] ) );
    fi;
end);

InstallGlobalFunction(OMI,
function(int)
    return Objectify( MitM_OMRecType
                    , rec( name := "OMI"
                         , content := [ String(int) ] ) );
end);

InstallGlobalFunction(OMF,
function(float)
    return Objectify( MitM_OMRecType
                    , rec( name := "OMF"
                         , attributes := rec( dec := String(float) ) ) );
end);

InstallGlobalFunction(OME,
function(oms, content)
    return Objectify( MitM_OMRecType
                    , rec( name := "OME"
                         , content := Concatenation( [oms], content ) ) );
end);

InstallGlobalFunction(OMV,
function(name)
    return Objectify( MitM_OMRecType
                    , rec( name := "OMV"
                         , attributes := rec( name := name )
                         , content := []  ) );
end);

InstallMethod(ViewString, "for an omrec",
              [MitM_OMRecRep],
function(r)
    local attr, i, content;
    if r!.name = "OMS" then
        if IsBound(r!.attributes.cdbase) then
            return StringFormatted( "OMS(cd=\"{}\", cdbase=\"{}\", name=\"{}\")"
                                  , r!.attributes.cd, r!.attributes.cdbase, r!.attributes.name );
        else
            return StringFormatted( "OMS(cd=\"{}\", name=\"{}\")"
                                  , r!.attributes.cd, r!.attributes.name );
        fi;
    elif r!.name = "OMA" then
        return StringFormatted( "OMA({})"
                              , JoinStringsWithSeparator(List(r!.content, ViewString), ", ") );
    elif r!.name = "OMSTR" then
        if IsEmpty(MitM_Content(r)) then
            return "OMSTR(\"\")";
        else
            return StringFormatted( "OMSTR(\"{}\")", MitM_Content(r)[1] );
        fi;
    elif r!.name = "OMI" then
        return StringFormatted( "OMI({})", r!.content[1] );
    elif r!.name = "OMF" then
        # TODO: this is not really right yet
        return StringFormatted( "OMF({})", r!.attributes.dec );
    elif r!.name = "OMATP" then
        attr := [];
        for i in [1, 3 .. Length(r!.content) - 1] do
            Add(attr, Concatenation(r!.content[i]!.attributes.name,
                                    " := ", ViewString(r!.content[i+1])));
        od;
        return StringFormatted( "OMATP( rec( {} ) )",
                                JoinStringsWithSeparator(attr, ", ") );
    elif r!.name = "OMATTR" then
        content := MitM_Content(r);
        return StringFormatted( "OMATTR( {}, {} )",
                                MitM_ATPToRec(content[1]),
                                ViewString(content[2]) );
    elif r!.name = "OMOBJ" then
        return StringFormatted( "OMOBJ({})", ViewString(MitM_Content(r)[1]) );
    elif r!.name = "OME" then
        return StringFormatted( "OME({}, {})",
                                ViewString(MitM_Content(r)[1]),
                                ViewString(MitM_Content(r)[2]) );
    else
        return StringFormatted( "ViewString for {} not implemented", r!.name );
    fi;
end);

InstallMethod(ViewObj, "for an omrec",
              [MitM_OMRecRep],
function(r)
    Print(ViewString(r));
end);

InstallMethod( MitM_Tag, "for an omrec",
               [MitM_OMRecRep],
r -> r!.name);

InstallMethod(MitM_Attributes, "for an omrec",
              [MitM_OMRecRep],
function(r)
    if IsBound(r!.attributes) then
        return r!.attributes;
    else
        return rec();
    fi;
end);

InstallMethod(MitM_Name, "for an omrec",
              [MitM_OMRecRep],
function(r)
    if IsBound(r!.attributes) and IsBound(r!.attributes.name) then
        return r!.attributes.name;
    else
        return fail;
    fi;
end);

InstallMethod(MitM_CD, "for an omrec",
              [MitM_OMRecRep],
function(r)
    if r!.name = "OMS" then
        return r!.attributes.cd;
    else
        return fail;
    fi;
end);

InstallMethod(MitM_CDBase, "for an omrec",
              [MitM_OMRecRep],
function(r)
    if r!.name = "OMS" and IsBound(r!.attributes.cdbase) then
        return r!.attributes.cdbase;
    else
        return fail;
    fi;
end);

InstallMethod(MitM_Content, "for an omrec",
              [MitM_OMRecRep],
function(r)
    if IsBound(r!.content) then
        return r!.content;
    else
        return fail;
    fi;
end);

InstallMethod(\=, "for omrec and omrec",
              [MitM_OMRecRep, MitM_OMRecRep],
{x, y} -> (x!.name = y!.name and
           IsBound(x!.content) = IsBound(y!.content) and
           ((not IsBound(x!.content)) or x!.content = y!.content)));

InstallMethod(\<, "for omrec and omrec",
              [MitM_OMRecRep, MitM_OMRecRep],
function(x, y)
    if x!.name < y!.name then
        return true;
    elif x!.name = y!.name then
        if (not IsBound(x!.content)) and IsBound(y!.content) then
            return true;
        elif IsBound(x!.content) and IsBound(y!.content) then
            if x!.content < y!.content then
                return true;
            fi;
        fi;
    fi;
    return false;
end);


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

Class(phi, Exp);

Class(SSAMap, rec(
     __call__ := self >> WithBases(self, rec(
	         operations:=PrintOps,
		 top := 0 ,
		 stack := [ ],
		 alternatives := [ ],
		 phi_map := tab()
	     )).enter(),
     print := self >> Print("SSAMap(", self.top, ")"),

     insert := meth(self, oldvar, newvar)
         self.stack[self.top].(oldvar.id) := newvar;
     end,

     _lookup := meth(self, v, i)
	 while i >= 1 do
	     if IsBound(self.stack[i].(v)) then return self.stack[i].(v); fi;
	     i := i-1;
	 od;
	 return false;
     end,

     lookup := (self, v) >> self._lookup(v.id, self.top),

     enter := meth(self)
         local newtab;
	 newtab := tab();
         Add(self.stack, newtab);
	 Add(self.alternatives, [newtab]);
	 self.top := self.top + 1;
	 return self;
     end,

     next := meth(self)
         local newtab;
	 newtab := tab();
         Add(self.alternatives[self.top], newtab);
	 self.stack[self.top] := newtab;
	 return self;
     end,

     join := meth(self)
         local i, alt, tt, id, phi_expr, phi_var, outer_tab, type, alts, tmp;

     #Create a list of all the changed variables in merging scope
#      changed := tab();

#      for alt in self.alternatives[self.top] do
#          for id in Filtered(NSFields(alt),x->x<>"__doc__") do
#              if not(IsBound(changed.(id))) then
#                  changed.(id) := (id);
#              fi;
#          od;
#      od;

     #add alternatives for when variable appear in two different branches
	 tt := tab();
	 for alt in self.alternatives[self.top] do
	     for id in Filtered(NSFields(alt),x->x<>"__doc__") do
	         if IsBound(tt.(id)) then Add(tt.(id).args, alt.(id));
		 else
		     tt.(id) := phi(alt.(id));
		     tt.(id).t := (alt.(id).t);
		 fi;
	     od;
	 od;

     #if a variable does not appear in all branches, maybe it should be set do its
     #father version
     alts := Length(self.alternatives[self.top]);
     for id in Filtered(NSFields(tt),x->x<>"__doc__") do
         if (Length(tt.(id).args)<>alts)  then
             tmp := self._lookup((id), self.top-1);
             if tmp<>false then
                 Add(tt.(id).args, tmp);
             fi;
         fi;
     od;

	 outer_tab := self.stack[self.top-1];
	 self.phi_map := tab(); # phiXX -> phi(a, b, c)
	 for id in Filtered(NSFields(tt),x->x<>"__doc__") do
	     phi_expr := tt.(id);
	     if Length(phi_expr.args) > 1 then # non-trivial phi function
		 phi_var := var.fresh_t("phi", phi_expr.args[1].t);
                 phi_expr.t := phi_var.t;
		 self.phi_map.(phi_var.id) := phi_expr;
		 outer_tab.(id) := phi_var;
		 # we make the following assumptions here
		 # 1. variables inside phi(...) are live outside the if
		 #    (violated when result of phi(...) is never used, eg. phi itself is dead,
		 #     this is safe, but will not remove dead code)
		 # 2. variables not inside phi(...) are dead outside the if
		 #    (violated in obscure cases like if(x) {t1=1} else {}; if(x) {y=t1}else{y=1},
		 #     we can prove that these cases do not arise in linear transforms)
		 for i in phi_expr.args do
		     i.live_out := true;
		 od;
	     else  # trivial phi(x)->x
             outer_tab.(id) := phi_expr.args[1];
	     fi;
	 od;
	 return self;
     end,

     leave := meth(self)
         local phi_map;
         phi_map := self.join();
         RemoveLast(self.stack, 1);
         RemoveLast(self.alternatives, 1);
	 self.top := self.top - 1;
	 return phi_map;
     end,

     subst_vars := (self, exp) >>
         FoldR(self.stack, (ex, t) -> SubstVars(ex, t), exp),

     phi_map_assigns := self >> List(Filtered(NSFields(self.phi_map),x->x<>"__doc__"),
	 f -> assign(var(f), self.phi_map.(f)))

));

Declare(_SSA);

SSA := function(code)
    local ssamap, icode;
    ssamap := SSAMap();
    icode := FlattenCode(_SSA(code, ssamap));
    return icode;
end;

_SSA := function(code, ssamap)
    local i, c, newvar, newc, phi_map;

    if ObjId(code) = IF then
	code.cond := ssamap.subst_vars(code.cond);
	ssamap.enter();	code.then_cmd := _SSA(code.then_cmd, ssamap);
	ssamap.next();	code.else_cmd := _SSA(code.else_cmd, ssamap);
	ssamap.leave();
	return chain(code, ssamap.phi_map_assigns());

    elif ObjId(code) = assign then
	return _SSA(chain(code), ssamap);

    elif IsExpCommand(code) then
	return ssamap.subst_vars(code);

    elif ObjId(code) = chain then
	for i in [1..Length(code.cmds)] do
	    c := code.cmds[i];
	    if ObjId(c) = assign then
		    c.exp := ssamap.subst_vars(c.exp);
		    if IsVar(c.loc) then
			newvar := var.fresh_t("t", c.loc.t);
			ssamap.insert(c.loc, newvar);
			c.loc := newvar;
		    else
			c.loc := ssamap.subst_vars(c.loc);
		    fi;
	    else
		code.cmds[i] := _SSA(c, ssamap);
	    fi;
	od;
	return code;
    elif IsRec(code) and IsBound(code.sideeffect) and code.sideeffect then
        return map_children_safe(code, x->ssamap.subst_vars(x));
    elif IsCommand(code) then
        return code;
    else
        Error("Can't handle ", ObjId(code));
    fi;
 end;

StronglyConnectedComponents := function(ll)
    local result, l, bool, r;

    result := [];
    for l in ll do
        bool := true;
        for r in [1..Length(result)] do
            if (bool=true) then
                if Length(Intersection(l, result[r]))<>0 then
                    result[r]:=Concatenation(result[r], l);
                    bool:=false;
                fi;
            fi;
        od;
        if (bool=true) then
            Add(result, l);
        fi;
    od;

    return result;
end;

EliminatePhiSSA := function(code)
    local elim_map, phis, p, v, master_var;
    elim_map := tab();
    phis := Collect(code, phi);

    phis := StronglyConnectedComponents(List(phis, x->x.args));

    for p in phis do
        master_var := p[1];
        for v in Drop(p, 1) do
	        elim_map.(v.id) := master_var;
    	od;
    od;
    code := SubstVars(code, elim_map);
    code := SubstBottomUp(code, phi, e->e.args[1]);
    return code;
end;


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

_TL_applicable := (e, p1, p2, p3, p4) ->
    p1 = e.N and p2 = e.n and IsPosInt(p3 / e.l) and p4 = e.r;

SIMD_ISA_DB.buildRules := meth(self)
    local isa;
    for isa in self.active() do
        if self.verbose then Print("Building rules for ", isa, "...\n"); fi;
        isa.buildRules();
    od;
end;

SIMD_ISA.buildRules := meth(self)
    self.rules := rec();
    self.rules.binrules := let(binrules := BuildBinRules(self), Concat(
        binrules,
#        BuildBinRulesLxI(self, binrules, true, true),
        BuildBinRulesLxI(self, binrules, true, false),
        BuildBinRulesLxI(self, binrules, false, true)
    ));
    self.rules.unrules := BuildUnRules(self);
    self.rules.x_I_vby2 := Build_x_I_vby2(self);
    self.rules_built := true;
end;

SIMD_ISA.supportedTL := self >> List(Concat(self.rules.binrules, self.rules.unrules),
        e -> rec(l := e.perm.l, N := e.perm.N, n := e.perm.n, r := e.perm.r));

SIMD_ISA.getTL := (self, p) >> let(res := Filtered(Concat(self.rules.binrules, self.rules.unrules),
        e -> _TL_applicable(e.perm, p[1], p[2], p[3], p[4])), When(Length(res) >= 1, res[1], false));

L_x_I_vby2_code := function(instr, N, n, vby2, y, x)
    local ichain, l, v, l1, l2, op, i;
    v := 2*vby2;
    l := L(N, n).lambda();
    ichain := [];
    for i in [0..N/2-1] do
        l1 := EvalScalar(l.at(2*i).ev());
        l2 := EvalScalar(l.at(2*i+1).ev());
        op := instr[(l1 mod 2)+1][(l2 mod 2)+1];
        Add(ichain, assign(vref(y, v*i, v), op.instr(vref(x, v*QuoInt(l1, 2), v), vref(x, v*QuoInt(l2, 2), v), op.p)));
    od;

    return chain(ichain);
end;

NewRulesFor(TL, rec(
    SIMD_ISA_Bases1 := rec(
        forTransposition := false,
        applicable := (self, t) >> t.isTag(1, AVecReg) and
                                     let(isa := t.firstTag().isa, P:=t.params,
                                         isa.active and ForAny(isa.supportedTL(),
                         e -> _TL_applicable(e, P[1], P[2], P[3], P[4]))
                                     ),
        apply := function(nt,C,cnt)
                    local isa, tl, ll, vprm, P;
            P:=nt.params;
                    isa := nt.firstTag().isa;
                    tl := isa.getTL(P);
                    ll := P[3] / tl.perm.l;
                    vprm := tl.vperm;
                    return When(ll = 1, vprm, BlockVPerm(ll, isa.v, vprm, tl.perm.spl));
                end,
    ),

    SIMD_ISA_Bases2 := rec(
        forTransposition := false,
        applicable := (self, t) >> t.isTag(1, AVecReg) and
                                     let(isa := t.firstTag().isa,
                                         isa.active and t.params[4] = isa.v / 2 and
                                         Flat(isa.rules.x_I_vby2) <> []
                                     ) and not(t.params[1]=t.params[2] or t.params[2]=1),
         apply := function(nt,C,cnt)
                    local isa, vperm, P;
            P:=nt.params;
                    isa := nt.firstTag().isa;
                    vperm := VPerm(Tensor(L(P[1], P[2]), I(P[4])),
                                  (y, x) -> L_x_I_vby2_code(isa.rules.x_I_vby2, P[1], P[2], P[4], y, x),
                                   isa.v, P[1] * P[4] / isa.v);
                    return When(P[3] = 1, vperm, Tensor(I(P[3]), vperm));
                end
    )

));


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

Class(altivec_common, SIMD_VMX, rec(
    unparser := altivecUnparser,
    #compileStrategy := self >> IndicesCS2,
    useDeref := true,

    vadd    := "vec_add",
    vsub    := "vec_sub",
    vmul    := "vec_madd",
    vmuladd := true,

    autolib := rec(
       includes := () -> ["<vec_types.h>", "<altivec.h>", "<spu2vmx.h>" ],
       timerIncludes := () -> ["<include/sp_powerpc_timer.h>" ],
    ),

    backendConfig := rec(
                        profile := default_profiles.linux_altivec_gcc,
                        measureFunction := _StandardMeasureVerify
                     ),
));

Class(altivec_4x32f, altivec_common, rec(
    active := false,
    isFixedPoint := false,
    info := "altivec 4 x 32-bit float",
    v := 4,
    ctype := "float",
    vtype := "vector float",
    stype := "__attribute__ ((aligned(16))) float",
    instr := [vunpacklo_4x32f_av, vunpackhi_4x32f_av, vperm_4x32f, vuperm_4x32f],
    bits := 32,
    isFloat := true,
    isFix := false,
    includes := () -> ["<altivec.h>", "<omega32.h>"],
    infix_op := false,
    infix_assign := true,
    vzero   := TVect(TReal, 4).zero(),# vzero_4x32f,
    vconst := vconstpr_av,
    vconstv := "(vector float)",
    vconst1 := "(vector float)",
    splopts := rec(precision := "single"),

));

SIMD_ISA_DB.addISA(altivec_4x32f);


#############################################################################
####
##
#A  anustab.gi                  ANUPQ package                  Eamonn O'Brien
#A                                                              Werner Nickel
##
#Y  Copyright 1993-2001,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
#Y  Copyright 1993-2001,  School of Mathematical Sciences, ANU,     Australia
##
#W  Greg Gamble reformulated the original code as a function and  then  split
#W  the original `anustab.g' into the declare/install files anustab.g[di].
##
##  Install file for function to  compute  the  stabiliser  of  an  allowable
##  subgroup; description is written to file LINK_output.
##

#############################################################################
##
#F  PqStabiliserOfAllowableSubgroup( <ANUPQglb>, <F>, <gens>, <relativeOrders>,
##  <ANUPQsize>, <ANUPQagsize>) . .  compute stabiliser of allowable subgroup
##
##  computes stabiliser and writes `LINK_output' output  file  for  the  `pq'
##  binary. The `LINK_output' file is written to `ANUPQData.tmpdir', but when
##  the `pq' binary was started up directly by the user that directory is set
##  to    the    current    directory.    The    argument    variables     of
##  `PqStabiliserOfAllowableSubgroup' are essentially the variables  assigned
##  to by `GAP_link_via_file.c' in the `pq' source code.
##
InstallGlobalFunction( PqStabiliserOfAllowableSubgroup,
function( ANUPQglb, F, gens, relativeOrders, ANUPQsize, ANUPQagsize )
local m, n, H, pcgs, aut, NumberAgAutos, i, imgs, NumberGlAutos, p, d,
      ANUPQMaxDegree, V, elm, baseU, id, baseN, infoLevelAutGrp, LINK_output,
      soluble, a, mat;

  # number of generators
  if Length( ANUPQglb.glAutos ) > 0 then
      m := Length (ANUPQglb.glAutos[1]);
  elif Length( ANUPQglb.agAutos ) > 0 then
      m := Length (ANUPQglb.agAutos[1]);
  else
      Error( "Panic: PqStabiliserOfAllowableSubgroup ",
             "called without automorphisms" );
  fi;

  # get the p-group
  n := Length (Pcgs(F));
  H := F / Subgroup (F, Pcgs(F){[m+1 .. n]}); IsPGroup(H);
  pcgs := Pcgs(H);

  # set up automorphism rec
  aut := rec();

  NumberAgAutos := Length (ANUPQglb.agAutos);
  aut.agAutos := [1..NumberAgAutos];
  for i in [1..Length (ANUPQglb.agAutos)] do
    MakeImmutable( ANUPQglb.genQ[i] );
    ConvertToMatrixRep( ANUPQglb.genQ[i], ANUPQglb.F );
    imgs := List( ANUPQglb.agAutos[i], x -> PcElementByExponents( pcgs, x ) );
    aut.agAutos[i] := PGAutomorphism( H, pcgs, imgs);
    aut.agAutos[i]!.mat := ANUPQglb.genQ[i];
  od;
  aut.agAutos := Reversed( aut.agAutos );
  aut.agOrder := Reversed (relativeOrders);

  NumberGlAutos := Length( ANUPQglb.glAutos );
  aut.glAutos := [1..NumberGlAutos];
  for i in [1..NumberGlAutos] do
    MakeImmutable( ANUPQglb.genQ[i] );
    ConvertToMatrixRep( ANUPQglb.genQ[NumberAgAutos + i], ANUPQglb.F );
    imgs := List( ANUPQglb.glAutos[i], x -> PcElementByExponents( pcgs, x ) );
    aut.glAutos[i] := PGAutomorphism( H, pcgs, imgs);
    aut.glAutos[i]!.mat := ANUPQglb.genQ[NumberAgAutos+ i];
  od;

  aut.field    := ANUPQglb.F;
  aut.group    := H;
  if ANUPQsize <> fail then
    aut.size     := ANUPQsize;
  fi;
  aut.one      := IdentityPGAutomorphism( H );
  aut.one!.mat := IdentityMat( ANUPQglb.q, aut.field );

  # add perm oper
  p := Size( aut.field );
  d := RankPGroup( aut.group );
  #compute perm rep having at most this degree
  ANUPQMaxDegree := 10000;
  if p^d <= ANUPQMaxDegree or ANUPQsize = fail then
    V := aut.field^d;
    if ANUPQsize = fail and p^d > ANUPQMaxDegree then
        Info(InfoANUPQ + InfoWarning, 1,
             "no. of elements in vector space V is large (", p^d, ")");
        Info(InfoANUPQ + InfoWarning, 1,
             "... this may take a while, if it succeeds at all!");
    fi;
    elm := AsSet( V );
    aut.glOper := [];
    for i in [1..NumberGlAutos] do
        a := aut.glAutos[i];
        mat := List(a!.baseimgs,
                    x -> ExponentsOfPcElement( pcgs, x ){[1..d]});
        mat := mat * One( aut.field );
        MakeImmutable( mat );
        ConvertToMatrixRep( mat, aut.field );
        aut.glOper[i] := Permutation( mat, elm, OnRight );
    od;
    #PrintTo("perms", "aut.glOper := ", aut.glOper,"; \n" );
    aut.glOrder := Size( Group( aut.glOper, () ) );
  else
    aut.glOrder := ANUPQsize / Size(ANUPQglb.F)^(ANUPQagsize);
  fi;

  Info(InfoANUPQ, 2, "Order of GL subgroup is ", aut.glOrder);
  Info(InfoANUPQ, 2, "No. of soluble autos is ", Length (aut.agAutos));

  # get allowable subgroup and nucleus
  baseU := gens * One (aut.field);
  id := IdentityMat( ANUPQglb.q, aut.field);
  baseN := id{[1..ANUPQglb.r]};

  # call stabilizer
  infoLevelAutGrp := InfoLevel( InfoAutGrp );
  if InfoLevel( InfoANUPQ ) >= 2 then
    SetInfoLevel( InfoAutGrp, 3 );
  fi;
  PGOrbitStabilizer( aut, baseU, baseN, false );

  if (Length (aut.glAutos) = 0) then
    soluble := -1;
  else
    soluble := 0;
  fi;

  aut.agAutos := Reversed (aut.agAutos);
  aut.agOrder := Reversed (aut.agOrder);

  LINK_output := Filename( ANUPQData.tmpdir, "LINK_output" );
  PrintTo (LINK_output, soluble);
  AppendTo (LINK_output, "\n");
  AppendTo (LINK_output, Length (aut.agAutos));
  AppendTo (LINK_output, "\n");
  for i in [1..Length (aut.agOrder)] do
    AppendTo (LINK_output, aut.agOrder[i]);
    AppendTo (LINK_output, "\n");
  od;
  AppendTo (LINK_output, Length (aut.agAutos) + Length (aut.glAutos) );
  AppendTo (LINK_output, "\n");
  for a in aut.agAutos do
    mat := List( a!.baseimgs, x -> ExponentsOfPcElement( pcgs, x ) );
    mat := Flat( mat );
    for m in mat do
        AppendTo (LINK_output, m, " " );
    od;
    AppendTo (LINK_output, "\n");
  od;
  for a in aut.glAutos do
    mat := List( a!.baseimgs, x -> ExponentsOfPcElement( pcgs, x ) );
    mat := Flat( mat );
    for m in mat do
        AppendTo (LINK_output, m, " " );
    od;
    AppendTo (LINK_output, "\n");
  od;
  SetInfoLevel( InfoAutGrp, infoLevelAutGrp );
end );

#E  anustab.gi  . . . . . . . . . . . . . . . . . . . . . . . . . . ends here


#
# cls 14: \tilde A_1
#
handle14char3:=function()
    local tmp1,tmp2,o,info,rep,cent;
    o:=AllClasses(orbs)[14];
    info:=infos[14];

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[12]);#pr[12]=[1,0,2,1]-->[0,1,2,1]
    tmp1:=ConjugateByTorus(tmp1,3,-1);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[12]);#pr[12]=[1,0,2,1]-->[0,1,2,1]
    tmp2:=ConjugateByTorus(tmp2,3,-1);

    return [tmp1,tmp2];
end;

#
# cls 12: A_2
#
handle12char3:=function()
    local tmp1,tmp2,o,info,rep,cent,u;
    o:=AllClasses(orbs)[12];
    info:=infos[12];

    rep:=Representative(o);
    #pr[7]=[0,0,1,1]-->[0,1,1,0]
    #pr[13]=[0,1,2,1]-->[1,1,2,0]
    tmp1:=ApplyRootsReflections(rep,[7,13]);
    u:=Unipotent(algebraicU(orbs),[[4,-1]]);
    tmp1:=Conj(tmp1,u);
    tmp1:=ConjugateByTorus(tmp1,2,-1);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[7,13]);
    tmp2:=Conj(tmp2,u);
    tmp2:=ConjugateByTorus(tmp2,2,-1);

    return [tmp1,tmp2];
end;

#
# cls 9: B_2
#
handle9char3:=function()
    local tmp1,tmp2,o,info,rep,cent;
    o:=AllClasses(orbs)[9];
    info:=infos[9];

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[9]);#pr[9]=[0,1,1,1]-->[1,1,1,0]
    tmp1:=ConjugateByTorus(tmp1,4,-1);# merge si cu 1

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[9]);#pr[9]=[0,1,1,1]-->[1,1,1,0]
    tmp2:=ConjugateByTorus(tmp2,4,-1);# merge si cu 1

    return [tmp1,tmp2];
end;

#
# cls 7: C_3(a_1)
#
handle7char3:=function()
    local tmp1,tmp2,o,info,rep,cent;
    o:=AllClasses(orbs)[7];
    info:=infos[7];

    rep:=Representative(o);
    tmp1:=ConjugateByTorus(rep,1,-1);# h_1 --> h_4
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,1,-1);# h_1 --> h_4
    return [tmp1,tmp2];
end;

#
# cls 6: F_4(a_3) [4,6,10,18] (finished?)
#
handle6char3:=function()
    local tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,o,info,rep,cent,uu;
    o:=AllClasses(orbs)[6];
    info:=infos[6];

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[3]);#pr[3]=[0,0,1,0]-->[0,0,1,0]
    uu:=Unipotent(chevalleyAdj(rep),[[2,-1],[6,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,2,3,4],[1,-1,aaa,aaa^2]);

    tmp3:=ApplyRootsReflections(rep,[5,1]);
    uu:=Unipotent(chevalleyAdj(rep),[[2,1]],Ordering(rep));#[1..24]);
    tmp3:=Conj(tmp3,uu);
    uu:=Unipotent(chevalleyAdj(rep),[[16,-1]],Ordering(rep));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjNegUa(tmp3,2,-1);
    uu:=Unipotent(chevalleyAdj(rep),[[13,1],[2,-1],[4,1]],Ordering(rep));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[3],[aaa]);

    tmp5:=ConjugateByTori(rep,[3],[-1]);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[3]);#pr[3]=[0,0,1,0]-->[0,0,1,0]
    uu:=Unipotent(chevalleyAdj(cent),[[2,-1],[6,1]]);
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,2,3,4],[1,-1,aaa,aaa^2]);

    tmp4:=ApplyRootsReflections(cent,[5,1]);
    uu:=Unipotent(chevalleyAdj(rep),[[2,1]],Ordering(cent));#[1..24]);
    tmp4:=Conj(tmp4,uu);
    uu:=Unipotent(chevalleyAdj(rep),[[16,-1]],Ordering(cent));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjNegUa(tmp4,2,-1);
    uu:=Unipotent(chevalleyAdj(rep),[[13,1],[2,-1],[4,1]],Ordering(cent));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjugateByTori(tmp4,[3],[aaa]);

    tmp6:=ConjugateByTori(cent,[3],[-1]);

    return [tmp1,tmp3,tmp5,tmp2,tmp4,tmp6];
end;

#
# cls 3: F_4(a_2) (not finished)
#
handle3char3:=function()
    local tmp1,tmp2,o,info,rep,cent;
    o:=AllClasses(orbs)[3];
    info:=infos[3];

    rep:=Representative(o);
    tmp1:=ConjugateByTori(rep,[4],[-1]);# h_1 --> h_4

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTori(cent,[4],[-1]);# h_1 --> h_4
    return [tmp1,tmp2];
end;

#
# cls 2: F_4(a_1)
#
handle2char3:=function()
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=AllClasses(orbs)[2];
    info:=infos[2];

    rep:=Representative(o);
    tmp1:=ConjugateByTori(rep,[3,1],[-1,-1]);# h_1 --> h_4, h_3 --> h_3
    uu:=Unipotent(chevalleyAdj(rep),[[3,-1]],Ordering(rep));#pr[3]=[0,0,1,0]-->[0,0,1,0]
    tmp1:=Conj(tmp1,uu);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTori(cent,[3,1],[-1,-1]);# h_1 --> h_4, h_3 --> h_3
    uu:=Unipotent(chevalleyAdj(cent),[[3,-1]],Ordering(cent));#pr[3]=[0,0,1,0]-->[0,0,1,0]
    tmp2:=Conj(tmp2,uu);

    return [tmp1,tmp2];
end;


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

_SKLR_ISA_TYPES := [
    [ T_Int(8),    "char"      ],
    [ T_Int(16),   "short"     ],
    [ T_Int(32),   "int"       ],
    [ T_Int(64),   "long long" ],
    [ T_UInt(8),   "unsigned char"      ],
    [ T_UInt(16),  "unsigned short"     ],
    [ T_UInt(32),  "unsigned int"       ],
    [ T_UInt(64),  "unsigned long long" ],
    [ T_Real(32),  "float"  ],
    [ T_Real(64),  "double" ],
    [ TInt,        "long"   ],
    [ TUInt,       "unsigned long"],
    [ TReal,       "double" ]
];

Class(SKLR, ISA, rec(
    __call__ := (self, el_t) >> WithBases(self, rec(
        t            := el_t,
	isSigned     := el_t.isSigned(),
	isFloat      := IsRealT(el_t),
	isFixedPoint := IsFixedPtT(el_t),
	operations   := ISAOps,
	print        := self >> Cond( self.t in List(_SKLR_ISA_TYPES, e -> e[1]),
	                                Print(self.id()), Print(self.__name__, "(", self.t, ")")),
	id           := self >> self.__name__ :: "_" :: el_t.strId(),
        ctype        := let( t := PositionProperty(_SKLR_ISA_TYPES, e -> e[1]=el_t),
                             Cond(t=false, "", _SKLR_ISA_TYPES[t][2])),
    )),

    isScalarISA  := true,

    v         := 1,
    getTags   := self >> [],
    getTagsCx := self >> [],
    toScalar  := self >> self,

    # this must be depricated:
    saturatedArithmetic := false,

));

IsSKLR_ISA := (x) -> IsRec(x) and IsBound(x.isScalarISA) and x.isScalarISA;

# register short variable names for SKLR(t) where t in  _SKLR_ISA_TYPES
# SKLR_i8, SKLR_ui8, ..., SKLR_f32, SKLR_f64, SKLR_f, SKLR_i
DoForAll( _SKLR_ISA_TYPES, t -> Assign(SKLR(t[1]).id(), SKLR(t[1])) );

# ISA.toScalar() returns scalar ISA of appropriate data type

SIMD_ISA.toScalar := self >> SKLR(self.gt());

ISA_Bridge.add(Class(CVT_SKLR_i16_i8,    ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(8)),  isa_to := SKLR(T_Int(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_i16_ui8,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(8)), isa_to := SKLR(T_Int(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui16_ui8,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(8)), isa_to := SKLR(T_UInt(16)) )));

ISA_Bridge.add(Class(CVT_SKLR_i32_i16,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(16)),  isa_to := SKLR(T_Int(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_i32_ui16,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(16)), isa_to := SKLR(T_Int(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_ui16, ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(16)), isa_to := SKLR(T_UInt(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_i64_i32,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_Int(64)) )));
ISA_Bridge.add(Class(CVT_SKLR_i64_ui32,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_Int(64)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui64_ui32, ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_UInt(64)) )));

ISA_Bridge.add(Class(CVT_SKLR_f32_i32,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_Real(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_f32_ui32,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_Real(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_f32_i64,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(64)),  isa_to := SKLR(T_Real(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_f32_ui64,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(64)), isa_to := SKLR(T_Real(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_f64_i32,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_Real(64)) )));
ISA_Bridge.add(Class(CVT_SKLR_f64_ui32,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_Real(64)) )));

ISA_Bridge.add(Class(CVT_SKLR_f64_i64,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Int(64)),  isa_to := SKLR(T_Real(64)) )));
ISA_Bridge.add(Class(CVT_SKLR_f64_ui64,  ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_UInt(64)), isa_to := SKLR(T_Real(64)) )));

ISA_Bridge.add(Class(CVT_SKLR_f64_f32,   ISA_Bridge_tcast, rec( props := [], isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_Real(64)) )));

# helper class: integer to larger unsigned integer with saturation on zero
Class(ISA_Bridge_IxIntToUInt, ISA_Bridge_I, rec(
    props    := ["saturation"],
    code     := (self, y, x, opts) >> assign( self._y(y,0), tcast(self.isa_to.t, max(self.isa_from.t.zero(), self._x(x,0))) )
));

ISA_Bridge.add(Class(CVT_SKLR_ui16_i8_wrap,  ISA_Bridge_wrap,        rec( isa_from := SKLR(T_Int(8)),  isa_to := SKLR(T_UInt(16)))));
ISA_Bridge.add(Class(CVT_SKLR_ui16_i8_sat,   ISA_Bridge_IxIntToUInt, rec( isa_from := SKLR(T_Int(8)),  isa_to := SKLR(T_UInt(16)))));
ISA_Bridge.add(Class(CVT_SKLR_ui32_i16_wrap, ISA_Bridge_wrap,        rec( isa_from := SKLR(T_Int(16)), isa_to := SKLR(T_UInt(32)))));
ISA_Bridge.add(Class(CVT_SKLR_ui32_i16_sat,  ISA_Bridge_IxIntToUInt, rec( isa_from := SKLR(T_Int(16)), isa_to := SKLR(T_UInt(32)))));
ISA_Bridge.add(Class(CVT_SKLR_ui64_i32_wrap, ISA_Bridge_wrap,        rec( isa_from := SKLR(T_Int(32)), isa_to := SKLR(T_UInt(64)))));
ISA_Bridge.add(Class(CVT_SKLR_ui64_i32_sat,  ISA_Bridge_IxIntToUInt, rec( isa_from := SKLR(T_Int(32)), isa_to := SKLR(T_UInt(64)))));

# now from larger to smaller
# helper class: larger data type to smaller with max-min saturation
Class(ISA_Bridge_IxSat, ISA_Bridge_I, rec(
    props    := ["saturation"],
    code     := (self, y, x, opts) >> let(
         r := self.isa_to.t.range(),
         a := self.isa_from.t.value(r.min),
         b := self.isa_from.t.value(r.max),
         assign( self._y(y,0), tcast(self.isa_to.t, max(a, min(b, self._x(x,0))))) )
));

#NOTE: add SReduce for max(Value, a) where Value is leq a.t.range().min
#NOTE: add SReduce for max(Value, max(Value, a))
ISA_Bridge.add(Class(CVT_SKLR_i32_i64_wrap,   ISA_Bridge_wrap,  rec( isa_from := SKLR(T_Int(64)),  isa_to := SKLR(T_Int(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_i32_i64_sat,    ISA_Bridge_IxSat, rec( isa_from := SKLR(T_Int(64)),  isa_to := SKLR(T_Int(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_i64_wrap,  ISA_Bridge_wrap,  rec( isa_from := SKLR(T_Int(64)),  isa_to := SKLR(T_UInt(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_i64_sat,   ISA_Bridge_IxSat, rec( isa_from := SKLR(T_Int(64)),  isa_to := SKLR(T_UInt(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_i32_ui64_wrap,  ISA_Bridge_wrap,  rec( isa_from := SKLR(T_UInt(64)), isa_to := SKLR(T_Int(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_i32_ui64_sat,   ISA_Bridge_IxSat, rec( isa_from := SKLR(T_UInt(64)), isa_to := SKLR(T_Int(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_ui64_wrap, ISA_Bridge_wrap,  rec( isa_from := SKLR(T_UInt(64)), isa_to := SKLR(T_UInt(32)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_ui64_sat,  ISA_Bridge_IxSat, rec( isa_from := SKLR(T_UInt(64)), isa_to := SKLR(T_UInt(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_i16_i32_wrap,   ISA_Bridge_wrap,  rec( isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_Int(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_i16_i32_sat,    ISA_Bridge_IxSat, rec( isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_Int(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui16_i32_wrap,  ISA_Bridge_wrap,  rec( isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_UInt(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui16_i32_sat,   ISA_Bridge_IxSat, rec( isa_from := SKLR(T_Int(32)),  isa_to := SKLR(T_UInt(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_i16_ui32_wrap,  ISA_Bridge_wrap,  rec( isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_Int(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_i16_ui32_sat,   ISA_Bridge_IxSat, rec( isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_Int(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui16_ui32_wrap, ISA_Bridge_wrap,  rec( isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_UInt(16)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui16_ui32_sat,  ISA_Bridge_IxSat, rec( isa_from := SKLR(T_UInt(32)), isa_to := SKLR(T_UInt(16)) )));

ISA_Bridge.add(Class(CVT_SKLR_i8_i16_wrap,    ISA_Bridge_wrap,  rec( isa_from := SKLR(T_Int(16)),  isa_to := SKLR(T_Int(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_i8_i16_sat,     ISA_Bridge_IxSat, rec( isa_from := SKLR(T_Int(16)),  isa_to := SKLR(T_Int(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui8_i16_wrap,   ISA_Bridge_wrap,  rec( isa_from := SKLR(T_Int(16)),  isa_to := SKLR(T_UInt(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui8_i16_sat,    ISA_Bridge_IxSat, rec( isa_from := SKLR(T_Int(16)),  isa_to := SKLR(T_UInt(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_i8_ui16_wrap,   ISA_Bridge_wrap,  rec( isa_from := SKLR(T_UInt(16)), isa_to := SKLR(T_Int(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_i8_ui16_sat,    ISA_Bridge_IxSat, rec( isa_from := SKLR(T_UInt(16)), isa_to := SKLR(T_Int(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui8_ui16_wrap,  ISA_Bridge_wrap,  rec( isa_from := SKLR(T_UInt(16)), isa_to := SKLR(T_UInt(8)) )));
ISA_Bridge.add(Class(CVT_SKLR_ui8_ui16_sat,   ISA_Bridge_IxSat, rec( isa_from := SKLR(T_UInt(16)), isa_to := SKLR(T_UInt(8)) )));

ISA_Bridge.add(Class(CVT_SKLR_f32_f64_trunc,   ISA_Bridge_wrap, rec( props := [], isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_Real(32)) )));

Class(ISA_Bridge_trunc_wrap, ISA_Bridge_I, rec(
    range := (self) >> self.isa_to.t.range(),
    props := ["trunc", "wraparound"],
    code  := (self, y, x, opts) >> assign( self._y(y,0), tcast(self.isa_to.t, self._x(x,0)) ),
));

ISA_Bridge.add(Class(CVT_SKLR_i32_f32_trunc,   ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_Int(32))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_f32_trunc,  ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_UInt(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_i32_f64_trunc,   ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_Int(64))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_f64_trunc,  ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_UInt(64)) )));

ISA_Bridge.add(Class(CVT_SKLR_i64_f32_trunc,   ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_Int(32))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui64_f32_trunc,  ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_UInt(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_i64_f64_trunc,   ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_Int(64))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui64_f64_trunc,  ISA_Bridge_trunc_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_UInt(64)) )));

Class(ISA_Bridge_round_wrap, ISA_Bridge_I, rec(
    range := (self) >> self.isa_to.t.range(),
    props := ["round", "wraparound"],
    code  := (self, y, x, opts) >> assign( self._y(y,0), tcast(self.isa_to.t, add(self._x(x,0), self.isa_from.t.value(0.5))) ),
));

ISA_Bridge.add(Class(CVT_SKLR_i32_f32_round,   ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_Int(32))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_f32_round,  ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_UInt(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_i32_f64_round,   ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_Int(64))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui32_f64_round,  ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(32)), isa_to := SKLR(T_UInt(64)) )));

ISA_Bridge.add(Class(CVT_SKLR_i64_f32_round,   ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_Int(32))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui64_f32_round,  ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_UInt(32)) )));

ISA_Bridge.add(Class(CVT_SKLR_i64_f64_round,   ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_Int(64))  )));
ISA_Bridge.add(Class(CVT_SKLR_ui64_f64_round,  ISA_Bridge_round_wrap, rec( isa_from := SKLR(T_Real(64)), isa_to := SKLR(T_UInt(64)) )));

ISA_Bridge.add(Class(CVT_SKLR_f32_clip32i, ISA_Bridge_I, rec(
    isa_from    := SKLR(T_Real(32)),
    isa_to      := SKLR(T_Real(32)),
    props       := ["saturation"],
    range       := (self) >> RangeT(self.clip.min, self.clip.max, T_Real(32).range().eps),
    clip        := rec( min := T_Int(32).range().min, max := T_Int(32).range().max ),
    code := (self, y, x, opts) >> assign( self._y(y,0), min(max(self._x(x,0), self.isa_from.t.value(self.clip.min)), self.isa_from.t.value(self.clip.max)) ),
)));

ISA_Bridge.add(Class(CVT_SKLR_f32_clip32ui, CVT_SKLR_f32_clip32i, rec(
    clip        := rec( min := T_UInt(32).range().min, max := T_UInt(32).range().max ),
)));

ISA_Bridge.add(Class(CVT_SKLR_f32_clip16i, CVT_SKLR_f32_clip32i, rec(
    clip        := rec( min := T_Int(16).range().min,  max := T_Int(16).range().max ),
)));

ISA_Bridge.add(Class(CVT_SKLR_f32_clip16ui, CVT_SKLR_f32_clip32i, rec(
    clip        := rec( min := T_UInt(16).range().min, max := T_UInt(16).range().max ),
)));

ISA_Bridge.add(Class(CVT_SKLR_f32_clip8i, CVT_SKLR_f32_clip32i, rec(
    clip        := rec( min := T_Int(8).range().min,  max := T_Int(8).range().max ),
)));

ISA_Bridge.add(Class(CVT_SKLR_f32_clip8ui, CVT_SKLR_f32_clip32i, rec(
    clip        := rec( min := T_UInt(8).range().min, max := T_UInt(8).range().max ),
)));


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

RewriteRules(RulesStrengthReduce, rec(

################## HALF ####################
    vunpacklo_half := Rule( vunpacklo_half,
        e -> vextract_half(vtrnq_half(e.args[1], e.args[2]), [0])),
    vunpackhi_half := Rule( vunpackhi_half,
        e -> vextract_half(vtrnq_half(e.args[1], e.args[2]), [1])),

################## NEON ####################
    vpacklo_neon := Rule( vpacklo_neon,
        e -> vextract_neon_4x32f(vuzpq_32f(e.args[1], e.args[2]), [0])),
    vpackhi_neon := Rule( vpackhi_neon,
        e -> vextract_neon_4x32f(vuzpq_32f(e.args[1], e.args[2]), [1])),
    vunpacklo_neon := Rule( vunpacklo_neon,
        e -> vextract_neon_4x32f(vzipq_32f(e.args[1], e.args[2]), [0])),
    vunpackhi_neon := Rule( vunpackhi_neon,
        e -> vextract_neon_4x32f(vzipq_32f(e.args[1], e.args[2]), [1])),
    vtransposelo_neon := Rule( vtransposelo_neon,
        e -> vextract_neon_4x32f(vtrnq_32f(e.args[1], e.args[2]), [0])),
    vtransposehi_neon := Rule( vtransposehi_neon,
        e -> vextract_neon_4x32f(vtrnq_32f(e.args[1], e.args[2]), [1])),
    vunpacklolo2_neon := Rule( vunpacklolo2_neon,
        e -> vextract_neon_4x32f(vtrnq_32f(vpacklo_neon(e.args[1], e.args[2]), vpackhi_neon(e.args[1], e.args[2])), [0])),
    vunpackhihi2_neon := Rule( vunpackhihi2_neon,
        e -> vextract_neon_4x32f(vtrnq_32f(vpacklo_neon(e.args[1], e.args[2]), vpackhi_neon(e.args[1], e.args[2])), [1])),

));


DZ2 schwarz,1,Projekt		//creates a zylinder
  S(1,1,2)			//redifines edges as (r,r,h)


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

canReorder := e -> not IsBound(e.doNotReorder);
Class(RulesSums, RuleSet);

RewriteRules(RulesSums, rec(
# Drop_DPWrapper := Rule([DPWrapper, @(1), ...], e->@(1).val),
 Drop_GrpISum := Rule([Grp, @(1, ISum)], e -> ISum(@(1).val.var, @(1).val.domain, @(1).val.child(1))),
 Drop_GrpInplaceISum := Rule([Grp, [Inplace, @(1, ISum)]], e -> Inplace(ISum(@(1).val.var, @(1).val.domain, @(1).val.child(1)))),

 # ================================================================
 # Associativity of Compose and SUM
 # ================================================================
 ComposeAssoc := ARule( Compose, [ @(1,Compose) ],  e -> @(1).val.children() ),
 SUMAssoc     := ARule( SUM,     [ @(1,SUM)     ],  e -> @(1).val.children() ),
 SUMAccAssoc  := ARule( SUMAcc,  [ @(1,SUMAcc)  ],  e -> @(1).val.children() ),
 TensorAssoc  := ARule( Tensor,  [ @(1,Tensor)  ],  e -> @(1).val.children() ),
 RedundandSUMAcc := Rule([SUMAcc, @(1)], e -> @(1).val),

 # ================================================================
 # Identity Matrix (I)
 # ================================================================
 Compose_IR := ARule(Compose, [@(1), I], e->[@(1).val]),
 Compose_IL := ARule(Compose, [I, @(1)], e->[@(1).val]),
 # rule Prm(fId()) -> I may break vectorization
 fId_toI := Rule([@(1, [Gath, Scat]), [fId, @(2)]], e -> I(@(2).val)),

 # ================================================================
 # Zero Matrix (O)
 # ================================================================
 ZeroSUMAcc1 := ARule(SUMAcc, [O, @(1)], e -> [@(1).val]),
 ZeroSUMAcc2 := ARule(SUMAcc, [@(1), O], e -> [@(1).val]),
 ZeroSUM1    := ARule(SUM, [O, @(1)], e -> [@(1).val]),
 ZeroSUM2    := ARule(SUM, [@(1), O], e -> [@(1).val]),
 ZeroTensor := Rule([Tensor, ..., @(1,O), ...], e -> O(Rows(e), Cols(e))),

 # ================================================================
 # Combining Gath, Scat, and Prm
 # ================================================================
 ComposeGathGath := ARule(Compose, [ @(1, Gath), @(2, [Gath, Prm]) ], # o 1-> 2->
     e -> [ Gath(fCompose(@(2).val.func, @(1).val.func)) ]),

 ComposePrmPrm := ARule(Compose, [ @(1, Prm), @(2, Prm) ], # o 1-> 2->
     e -> [ Prm(fCompose(@(2).val.func, @(1).val.func)) ]),

 ComposeScatScat := ARule(Compose, [ @(1, [Scat, ScatAcc]), @(2, [Scat, ScatAcc]) ], # <-1 <-2 o
     e -> [ Cond(ObjId(@(1).val)=ScatAcc or ObjId(@(2).val)=ScatAcc,
                 ScatAcc(fCompose(@(1).val.func, @(2).val.func)),
                 Scat   (fCompose(@(1).val.func, @(2).val.func))) ]),

 ComposePrmScat  := ARule(Compose, [ @(1, Prm), @(2, [Scat, ScatAcc]) ], # 1-> <-2 o
     e -> [ ObjId(@(2).val)(fCompose(@(1).val.func.transpose(), @(2).val.func)) ]),

 ComposeDPrmDPrm   := ARule(Compose, [ @(1, DelayedPrm), @(2, DelayedPrm) ], # direct:  o 1-> 2->
     e -> [ DelayedPrm(fCompose(@(2).val.func, @(1).val.func)) ]),

 DropGathScat := ARule( Compose, [ @(1, Gath), @(2, Scat, x->x.transpose()=@(1).val) ],
     e -> []),

 # ================================================================
 # ScatGath
 # NOTE: YSV ScatGath is ugly and should be removed
 # ================================================================
 ComposeScat_ScatGath := ARule(Compose, [ @(1, Scat), @(2, ScatGath) ], # <-1 <-2 o
     e -> [ ScatGath(fCompose(@(1).val.func, @(2).val.sfunc), @(2).val.gfunc) ]),

 ComposeScatGath_Gath := ARule(Compose, [ @(1, ScatGath), @(2, Gath) ], # o 1-> 2->
     e -> [ ScatGath(@(1).val.sfunc, fCompose(@(2).val.func, @(1).val.gfunc)) ]),

 # ================================================================
 # Using distributivity to pull Prm, Gath, Scat into ISum, SUM, etc.
 # ================================================================
 PullInRight := ARule( Compose,
       [ @(1, [Prm, Scat, ScatAcc, TCast, PushR, PushLR, Conj, ConjL, ConjR, ConjLR, FormatPrm]),
         @(2, [RecursStep, Grp, BB, SUM, Buf, ISum, Data, COND, NoDiagPullin, NoDiagPullinLeft, NoDiagPullinRight, NeedInterleavedComplex]) ],
  e -> [ CopyFields(@(2).val, rec(
             _children :=  List(@(2).val._children, c -> @(1).val * c),
             dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

 PullInLeft := ARule( Compose,
       [ @(1, [RecursStep, Grp, BB, SUM, SUMAcc, Buf, ISum, ISumAcc, Data, COND, NoDiagPullin, NoDiagPullinLeft, NoDiagPullinRight, NeedInterleavedComplex]),
         @(2, [Prm, Gath, TCast, PushL, PushLR, Conj, ConjL, ConjR, ConjLR, FormatPrm]) ],
     e -> [ CopyFields(@(1).val, rec(
                _children := List(@(1).val._children, c -> c * @(2).val),
                dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

 ComposePushRPrmScat := ARule(Compose, [ @(1, PushR, e -> ObjId(e.child(1)) = Prm), @(2, Scat) ],
    e -> [ Scat(fCompose(@(1).val.child(1).func.transpose(), @(2).val.func)) ]),

 # Match Gath * PushL(Prm) structure. The normal Gath * PushL rule occasionally
 # does not apply properly -- not clear why! (MRT)
 ComposeGathPushLPrm := ARule(Compose, [ @(1, Gath), @(2, PushL, e -> ObjId(e.child(1)) = Prm) ],
    e -> [ Gath(fCompose(@(2).val.child(1).func, @(1).val.func)) ]),

 # ================================================================
 # Low Priority
 # ================================================================
 ComposeScatPrm  := ARule(Compose, [ @(1, [Scat, ScatAcc]), @(2, Prm) ],  # <-1 2-> o
     e -> [ ObjId(@(1).val)(fCompose(@(1).val.func, @(2).val.func.transpose())) ]),

 ComposePrmGath  := ARule(Compose, [ @(1, Prm), @(2, Gath) ], # o 1-> 2->
     e -> [ Gath(fCompose(@(2).val.func, @(1).val.func)) ]),

 # ================================================================
 # TCast
 # ================================================================
 ComposeConvScat  := ARule(Compose, [ @(1, TCast), @(2, [Scat, ScatAcc]) ],  # <-1 2-> o
     e -> [ @(2).val, TCast(Cols(@(2).val), @(1).val.params[2], @(1).val.params[3]) ]),

 ComposeGathConv  := ARule(Compose, [ @(1, Gath), @(2, TCast) ], # o 1-> 2->
     e -> [ TCast(Rows(@(1).val), @(2).val.params[2], @(2).val.params[3]), @(1).val ]),

 # ================================================================
 # ConjTranspose
 # ================================================================
 InertConjTranspose_@ := Rule(
     [InertConjTranspose, @.cond(e->not (IsBound(e.isInertConjTranspose) and e.isInertConjTranspose()))],
     e -> e.child(1).conjTranspose()),
));

# ================================================================
# COND Rules
# ================================================================

# NOTE: this replication should not be necessary
# Rule([@(0,SUMAcc), ..., @(1,COND), ...],
#     e -> let( ch := e.children(),
#         left := Left(...),
#         right := Right(...),
#         leftch := ch{[1..left]},
#         rightch := ch{[right..Length(ch)]},
#         OP := ObjId(@(0).val),
#         COND(@(1).val.cond,
#         List(@(1).val.children(),
#              c -> OP(Concatenation(Copy(leftch), [c], Copy(rightch)))))));

# ================================================================
# OL Rules
# ================================================================

Class(OLRules, RuleSet);
RewriteRules(OLRules, rec(
  Gath_fCross := Rule([Gath, @(1, fCross)], e -> Cross(List(@(1).val.rChildren(),x->Gath(x)))),

  fCross_fCross := ARule(fCompose, [@(1,fCross), @(2,fCross)],
      e-> [fCross(List([1..Length(@(1).val.rChildren())],i->fCompose(@(1).val.rChildren()[i], @(2).val.rChildren()[i])))]),

  f2DTensor_f2DTensor := ARule(fCompose, [@(1,f2DTensor), @(2,f2DTensor)],
      e-> [f2DTensor(List([1..Length(@(1).val.rChildren())],i->fCompose(@(1).val.rChildren()[i], @(2).val.rChildren()[i])))]),

  f4DTensor_f4DTensor := ARule(fCompose, [@(1,f4DTensor), @(2,f4DTensor)],
      e-> [f4DTensor(List([1..Length(@(1).val.rChildren())],i->fCompose(@(1).val.rChildren()[i], @(2).val.rChildren()[i])))]),

  f2DTr_f2DTensor := ARule(fCompose, [@(1,f2DTr), @(2,f2DTensor)],
      e-> let(f:=f2DTensor(Reversed(@(2).val.rChildren())),
              [f, ApplyFunc(ObjId(@(1).val), Reversed(f.advdomain()[1]))])),

  f2DTr_f2DTr := ARule(fCompose, [@(1,f2DTr), @(2,f2DTr)],
      e-> []),

  f2DTensor_f2DTrExplicit := ARule(fCompose, [@(1,f2DTensor), @(2, [f2DTrExplicitL, f2DTrExplicitR])],
      e-> let(f:=f2DTensor(Reversed(@(1).val.rChildren())),
              [ApplyFunc(ObjId(@(2).val), Reversed(f.advrange()[1])), f])),

  f2DTr_f2DTrExplicit := ARule(fCompose, [@(1,f2DTr), @(2,[f2DTrExplicitL, f2DTrExplicitR])],
      e-> []),

  f2DTrExplicitRL := ARule(fCompose, [@(1,f2DTrExplicitR), @(2,f2DTrExplicitL)],
      e-> []),

  ParSeq_ParSeq := Rule([@(1, ParSeq), ..., @(2, ParSeq, x -> x.fb_cnt=@(1).val.fb_cnt), ...],
      e-> ParSeq(e.fb_cnt, Flat(List(e.children(), x -> Cond( x _is ParSeq and x.fb_cnt=e.fb_cnt, x.children(), x))))),
));


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

#Don't forget to Import(platforms.intel);

#_svctSizes := (N, p, v) -> Filtered([1..N], i->ForAll(Factors(i), j->j<=p) and IsInt(i/v^2));
#_defaultSizes := (func, default) -> ((arg) -> When(Length(arg) = 1, func(When(IsList(arg[1]), arg[1], [arg[1]])), func(default)));

_cores := 4;
_vlen := 4;
_maxN := 16;
_factors :=  [1, 2, 3, 5];
_parSizes := (N, p, v) -> List(Set(Filtered(Flat(Map(Cartesian(v^2*p^2 * List([1.._maxN], i->2^i), _factors), Product)), i->i<=2^N and i>v^2*p^2)));
_large := List([10..20], i->2^i);

_smpOpts := (intlCmplx, buf, omp) -> rec(use_functions := false, use_openmp := omp, use_buffering := buf, interleavedComplex := intlCmplx,
    simd_opts:=rec(svct:=true, splitL:=false, oddSizes:=false));

benchCore2 := function()
    if LocalConfig.cpuinfo.SIMD().hasSSE3() and LocalConfig.cpuinfo.cores >= 2 then
        return rec(
            OpenMP_2x64f := rec(
                split := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, false, true)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, true, true)), _large)
                ),
                interleaved := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, LocalConfig.cpuinfo.cores, s, _smpOpts(true, false, true)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, 1, s, _smpOpts(true, true, true)), _large)
                )
            ),
            OpenMP_4x32f := rec(
                split := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, false, true)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, true, true)), _large),

                ),
                interleaved := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(true, false, true)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(true, true, true)), _large)
                )
            ),
            threads_2x64f := rec(
                split := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, false, false)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, true, false)), _large)

                ),
                interleaved := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, LocalConfig.cpuinfo.cores, s, _smpOpts(true, false, false)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_2x64f, 1, s, _smpOpts(true, true, false)), _large)
                )
            ),
            threads_4x32f := rec(
                split := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, false, false)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(false, true, false)), _large)

                ),
                interleaved := rec(
                    medium := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(true, false, false)), _parSizes(_maxN, _cores, _vlen)),
                    large := _defaultSizes(s->spiral.libgen.doParSimdDft(SSE_4x32f, LocalConfig.cpuinfo.cores, s, _smpOpts(true, true, false)), _large)
                )
            )
        );
    else
        return false;
    fi;
end;