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

FFTE_copyright :=
"/*\n"::
"\n"::
"     FFTE: A FAST FOURIER TRANSFORM PACKAGE\n"::
"\n"::
"     (C) COPYRIGHT SOFTWARE, 2000-2004, 2008-2014, ALL RIGHTS RESERVED\n"::
"                BY\n"::
"         DAISUKE TAKAHASHI\n"::
"         FACULTY OF ENGINEERING, INFORMATION AND SYSTEMS\n"::
"         UNIVERSITY OF TSUKUBA\n"::
"         1-1-1 TENNODAI, TSUKUBA, IBARAKI 305-8573, JAPAN\n"::
"         E-MAIL: daisuke@cs.tsukuba.ac.jp\n"::
"\n"::
"\n"::
"     WRITTEN BY DAISUKE TAKAHASHI\n\n"::
"     THIS KERNEL WAS GENERATED BY SPIRAL "::Version()::"\n"::
"*/\n\n";


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

InstallGlobalFunction(PlotSpectraPermsInScilab,
function(perms, deg, opts)
  local mats, i, j,
  temp_dir, temp_file, temp_file_name, sci_temp_file, sci_temp_file_name,
  plot_spectra_func_file, exec_string,
  round, stacksize, output_filename, title;

  if IsBound(opts.round) then round := opts.round; else round := 7; fi;
  if IsBound(opts.stacksize) then stacksize := opts.stacksize; else stacksize := AG_Globals.scilab_stacksize; fi;
  if IsBound(opts.output) then output_filename := opts.output; else output_filename := ""; fi;
  if IsBound(opts.title) then title := opts.title; else title := ""; fi;

  plot_spectra_func_file := Filename(DirectoriesPackageLibrary("automgrp","scilab"),
                                     "PlotSpectraPermsInScilab.sci");
  if plot_spectra_func_file = fail then
    Print("error in PlotSpectraPermsInScilab:\n  scilab file not found\n");
    return fail;
  fi;

  temp_dir := DirectoryTemporary();
  temp_file_name := Filename(temp_dir, "pass");
  temp_file := OutputTextFile(temp_file_name, false);
  if temp_file = fail then
    Print("error in PlotSpectraPermsInScilab:\n  could not create temp file\n");
    return fail;
  fi;

  for i in [1..deg] do
    for j in [1..Length(perms)] do
      AppendTo(temp_file, i, "\t", i^perms[j], "\t");
    od;
    AppendTo(temp_file, "\n");
  od;
  CloseStream(temp_file);

  sci_temp_file_name := Filename(temp_dir, "commands_for_scilab");
  sci_temp_file := OutputTextFile(sci_temp_file_name, false);

  if sci_temp_file = fail then
    Print("error in PlotSpectraPermsInScilab:\n",
          "  Could not create temp file for scilab script\n");
    return fail;
  fi;

  SetPrintFormattingStatus(sci_temp_file, false);

  PrintTo(sci_temp_file, "getf(\"", plot_spectra_func_file, "\");\n");
  PrintTo(sci_temp_file, "PlotSpectraPermsInScilab(",
                         "\"", temp_file_name, "\", ",
                         Length(perms), ", ",
                         deg, ", ",
                         round, ", ",
                         stacksize, ", ",
                         "\"", output_filename, "\", ",
                         "\"", title, "\");\n");

  if output_filename <> "" then
    PrintTo(sci_temp_file, "exit\n");
  fi;

  CloseStream(sci_temp_file);

  exec_string := Concatenation(#"cat ", sci_temp_file_name, "; ",
                               "xterm -e scilab -nw -f ",
                               sci_temp_file_name);

  if output_filename = "" then
    Append(exec_string, " &");
  fi;

  Exec(exec_string);
end);

#############################################################################
##
##  PlotSpectraInScilab(<list>, <level>[, <opts>])
##
InstallMethod(PlotSpectraInScilab,
              [IsList and IsTreeAutomorphismCollection, IsPosInt],
function(gens, level)
  PlotSpectraPermsInScilab(List(gens, g -> PermOnLevel(g, level)),
                           DegreeOfTree(gens[1])^level, rec());
end);

InstallMethod(PlotSpectraInScilab,
              [IsList and IsTreeAutomorphismCollection, IsPosInt, IsRecord],
function(gens, level, opts)
  PlotSpectraPermsInScilab(List(gens, g -> PermOnLevel(g, level)),
                           DegreeOfTree(gens[1])^level, opts);
end);

InstallMethod(PlotSpectraInScilab,
              [IsTreeAutomorphismGroup, IsPosInt],
function(G, level)
  PlotSpectraInScilab(GeneratorsOfGroup(G), level, rec());
end);

InstallMethod(PlotSpectraInScilab,
              [IsTreeAutomorphismGroup, IsPosInt, IsRecord],
function(G, level, opts)
  PlotSpectraInScilab(GeneratorsOfGroup(G), level, opts);
end);

# InstallOtherMethod(PlotAutomatonSpectraInScilab, [IsList, IsInt, IsInt, IsInt],
# function(list, iter_num, round, stacksize)
#   local mats, i, j,
#   temp_dir, temp_file, temp_file_name, sci_temp_file, sci_temp_file_name,
#   plot_spectra_func_file, exec_string;
#
#   plot_spectra_func_file := "/home/muntyan/math/automata/scilab/plot_spectra.sci";
#
#   ## TODO: input checking
#
#   mats := PermMatrices(list, iter_num);
#
#   temp_dir := DirectoryTemporary();
#   temp_file_name := Filename(temp_dir, "pass");
#   temp_file := OutputTextFile(temp_file_name, false);
#   if temp_file = fail then
#     Error("Could not create temp file\n");
#   fi;
#
#   for i in [1..2^iter_num] do
#     for j in [1..Length(list)] do
#       AppendTo(temp_file, mats[j][i][1], "\t", mats[j][i][2], "\t");
#     od;
#     AppendTo(temp_file, "\n");
#   od;
#   CloseStream(temp_file);
#
#   sci_temp_file_name := Filename(temp_dir, "pass_sci");
#   sci_temp_file := OutputTextFile(sci_temp_file_name, false);
#   if sci_temp_file = fail then
#     Error("Could not create temp file for scilab script\n");
#   fi;
#
#   AppendTo(sci_temp_file, "getf(\"", plot_spectra_func_file, "\");\n");
#   AppendTo(sci_temp_file, "plot_spectra(\"", temp_file_name, "\", ", Length(list), ", ", iter_num, ", ", round, ", ", stacksize, ");\n");
# #  AppendTo(sci_temp_file, "exit\n");
#   CloseStream(sci_temp_file);
#
#   exec_string := Concatenation("xterm -e scilab -nw -f ", sci_temp_file_name, " > /dev/null");
#   Exec(exec_string);
# end);
#
#
# InstallMethod(PlotAutomatonSpectraInScilab, [IsList, IsInt, IsInt],
# function(list, iter_num, round)
#   PlotAutomatonSpectraInScilab(list, iter_num, round, 10000000);
# end);
#
#
# InstallOtherMethod(PlotAutomatonSpectraInScilab, [IsList, IsInt],
# function(list, iter_num)
#   PlotAutomatonSpectraInScilab(list, iter_num, 7, 10000000);
# end);


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

Class(BarrierScratchUnparserProg, CScratchUnparserProg, rec(
    barrier_cmd := (self,o,i,is) >> Print(Blanks(i), self.opts.barrierCMD(self.opts), self.pinfix(o.args, ", "), ";\n"),
    nop_cmd := (self,o,i,is) >> Print(""),
	register := (self,o,i,is) >> Print(Blanks(i), "if(count == 0) ", self.opts.register(self.opts), self.pinfix(o.args, ", "), ";\n"),
    initialization := (self, o, i, is) >> Print(Blanks(i), self.opts.initialization(self.opts), self.pinfix(o.args, ", "), ";\n"),
	par_exec := (self,o,i,is) >> Print(Blanks(i), "parallel((void*)&sub_cpu) \n",
                              Blanks(i), "{\n", self(o.cmds[1],i+is,is),
                              Blanks(i),"}\n"),
));


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

_unTspl := (c, info) -> When(spiral.paradigms.common.IsTSPL(c), SumsSPL(c.toSpl(), info.opts), c);

SumsRuleTreeStep := function ( rtwrap, info )
  local S, rt, t, tag, tags, processed_tags;
  rt := rtwrap.rt;
  info.rsteps := info.rsteps + 1;

  tags := When(IsBound(rt.node.getTags), rt.node.getTags(), []);

  if IsBound(rt.node.noCodelet) then
      S := SumsSPL(_SPLRuleTree(rt), info.opts);

  else
      processed_tags := Union(info.processed_tags, List(tags, x->x.kind()));
      S := ApplyRuleTreeStep(rt, c -> Cond(
          not IsRuleTree(c) and IsBound(c.noCodelet) and c.noCodelet,
	      _unTspl(c, info),
	  not IsRuleTree(c),
	      RecursStep(_unTspl(c, info)).setA(processed_tags => processed_tags),
          IsBound(c.node.noCodelet) and c.node.noCodelet,
	      _SPLRuleTree(c),
	  # else
              RecursStep(RTWrap(c)).setA(processed_tags => processed_tags)
      ));
      S := SumsSPL(S, info.opts);
      S.root := rt.node;
  fi;

  # tags may inject container objects
  for tag in Reversed(tags) do
      if IsBound(tag.container) and not (tag.kind() in info.processed_tags) then
          S := tag.container(S);
      fi;
  od;
  return S;
end;

SumsRecursStep := function ( rstep, info )
  local rsteps;
  rsteps := info.rsteps;
  info.processed_tags := When(IsBound(rstep.a.processed_tags), rstep.a.processed_tags, []);
  rstep := SubstTopDownNR(rstep, RTWrap, e -> SumsRuleTreeStep(e, info));
  if rsteps = info.rsteps then # nothing has changed
      return rstep;
  else
      rstep := ApplyStrategy(rstep, info.strategy, UntilDone, info.opts);
      return rstep.child(1); # strip outer RecursStep container
  fi;
end;

Recurse := function(sums,info)
    local rt;
    if IsRuleTree(sums) then
	rt := sums;
	# keep track of tags that were converted into containers, via an attribute
	# this mechanism does not work well, and more VContainers will be created
	# than neeeded, however this is safe, because redundant ones are eliminated
	# by rewrite rules.
        return Recurse(RecursStep(RTWrap(rt)).setA(processed_tags=>[]), info);

    else
        return SubstTopDownNR(sums, RecursStep, x -> SumsRecursStep(x,info));
    fi;
end;

SumsRuleTreeStrategy := function ( rt, strategy, opts )
    local info;
    info := rec(rsteps := 0, strategy := strategy, opts := opts); #, cutoff := cutoff_func);
    rt := Recurse(rt, info);
    while info.rsteps > 0 do info.rsteps := 0; rt := Recurse(rt, info); od;
    return rt;
end;

#F SumsRuleTree(<rt>, <opts>)
#F
#F <opts> flags used:
#F   opts.formulaStrategies.sigmaSpl    Sigma-SPL rewriting strategy
#F   opts.formulaStrategies.rc          RC(.) rewriting strategy
#F   opts.generateComplexCode == bool   if set to false, then RC rewriting strategy is applied
#F
SumsRuleTree := function(rt, opts)
        local sums, rsums, t, tag;
        if IsNonTerminal(rt) then rt := RandomRuleTree(rt,opts); fi;
        sums := SumsRuleTreeStrategy(rt, [], opts);
        sums := ApplyStrategy(sums, opts.formulaStrategies.sigmaSpl, UntilDone, opts);
        sums := ApplyStrategy(sums, opts.formulaStrategies.preRC, UntilDone, opts);
        if (not opts.generateComplexCode) and (not rt.node.isReal()) then # NOTE: when t_in/t_out available check them instead of isReal
            rsums := ApplyStrategy(RC(sums), opts.formulaStrategies.rc, UntilDone, opts);
        else
            rsums := sums;
        fi;
        rsums := ApplyStrategy(rsums, opts.formulaStrategies.postProcess, UntilDone, opts);
        rsums := SumsUnification(rsums, opts);
        rsums.ruletree := rt;

        return rsums;
end;

#F See SumsRuleTree
#F
SumsRuleTreeOpts := SumsRuleTree;

RecurseOpts := function(rt, opts)
    local sums, rsums;
    if IsNonTerminal(rt) then rt := RandomRuleTree(rt,opts); fi;
    sums := Recurse(rt, rec(strategy:=opts.formulaStrategies.sigmaSpl, rsteps:=0, opts := opts));
    return sums;
    #return ApplyStrategy(sums, opts.formulaStrategies.postProcess, UntilDone, opts);
end;

######
SumsVerifyRulesForSPL := (S,opts) -> VerifyRules(S,
    (rt, s) -> InfinityNormMat(MatSPL(SumsRuleTree(rt,opts)) - MatSPL(s)) < 1e-11,
    opts);


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

# Top-Level Functions for SPIRAL
# ==============================
# BWS, MP, from 02/15/00

#F Creating Transforms
#F -------------------
#F

#F Transforms
#F   the list of symbols for valid transforms, e.g., "DFT".
#F
Transforms := NonTerminalListSPL;

#F Transform ( <symbol>, <parameters> )
#F   alias for SPLNonTerminal, returns the transform defined by
#F   <symbol> and <parameters>, e.g. Transform( "DFT", 8 ).
#F
#Transform := SPLNonTerminal;

#F Info ( <symbol> )
#F   prints information on how to create the transform <symbol>
#F
Info := Doc;

#F toSPL( <obj> )
#F   Converts ruletree or nonterminal (using random ruletree) into an SPL,
#F   If <obj> is an SPL it is returned as is.
toSPL := x ->
    Cond(IsNonTerminal(x), SPLRuleTree(RandomRuleTree(x)),
	 IsSPL(x), x,
	 IsRuleTree(x), SPLRuleTree(x),
	 Error("<x> must be an SPL or a RuleTree"));


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

Class(FloatMixin, rec(
    XType := TPtr(T_Real(32)), precision := "single", TRealCtype := "float")
);

Class(DoubleMixin, rec(
    XType := TPtr(T_Real(64)), precision := "double", TRealCtype := "double")
);

Class(CoSynthesizeStrategies, rec(
    C_opt := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOL, RulesTerminateReductionHCOL, RulesSumsHCOL], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHCOL, opts),
		(c, opts) -> Rewrite(c, RulesTypeHCOL, opts)		],
    C_opt_useAbsMax := [ (t, opts) -> RandomRuleTree(t, opts),
		(rt, opts) -> SPLRuleTree(rt),
		(s, opts) -> SumsSPL(s, opts),
		(s, opts) -> Rewrite(s, [RulesSumsHCOL], opts),
		(s, opts) -> Rewrite(s, [RulesSumsHCOL, RulesTerminateReductionHCOL, RulesSumsHCOL], opts),
		(s, opts) -> HCOLProof_Codegen(s, opts),
		(c, opts) -> Rewrite(c, RulesCodeUnrollHCOLuseAbsMaxSR, opts) ],
    C_raw := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOL, RulesTerminateReductionHCOL, RulesSumsHCOL], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts) ],
    C_sim := [        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOL, RulesTerminateReductionHCOL, RulesSumsHCOL], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts),
	(c, opts) -> Rewrite(c, RulesCodeUnrollHCOL, opts) ],
	no_opt := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOL, RulesTerminateReductionHCOL, RulesSumsHCOL], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts) ]
));

Class(HCOLopts, rec(
    getOpts := meth(arg)
        local opts;

        opts := CopyFields(SpiralDefaults,
            rec(
                codegen := HCOLCodegen,
                sumsgen := HCOLSumsGen,
                params := [],
                includes := [],
                unparser := HCOLUnparser,
                operations := rec(Print := (s) -> Print("<HCOL options"::When(Length(arg)>1 and IsBound(arg[2].name), ", "::arg[2].name, "")::">")),
                funcgen := HCOLFuncGen,
                useCReturn := false,
				errorCheck := false,
                YType := TPtr(TInt),
                csStrategy := CoSynthesizeStrategies.C_opt
            ),
            When(Length(arg)=2, arg[2]));
        return opts;
    end
));


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

Declare(ScatPtr);

Class(GathPtr, Gath, rec(
    rChildren := self >> [self.ptr, self.func],
    rSetChild := rSetChildFields("ptr", "func"),
    new := (self, ptr, func) >> SPL(WithBases(self, rec(
        ptr := ptr,
      	func := Checked(IsFunction(func) or IsFuncExp(func), func)))).setDims()
));

Class(ScatPtr, Scat, rec(
    rChildren := self >> [self.ptr, self.func],
    rSetChild := rSetChildFields("ptr", "func"),
    new := (self, ptr, func) >> SPL(WithBases(self, rec(
        ptr := ptr,
	    func := Checked(IsFunction(func) or IsFuncExp(func), func)))).setDims()
));

Class(OO, O);


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

Class(CScratchUnparserProg, CUnparser, rec(
    dma_signal := (self,o,i,is) >> Print(Blanks(i), self.opts.dmaSignal(self.opts), self.pinfix(o.args, ", "), ";\n"),
    dma_wait := (self,o,i,is) >> Print(Blanks(i), self.opts.dmaWait(self.opts), "", self.pinfix(o.args, ", "), ";\n"),

    cpu_signal := (self,o,i,is) >> Print(Blanks(i), self.opts.cpuSignal(self.opts), self.pinfix(o.args, ", "), ";\n"),
    cpu_wait := (self,o,i,is) >> Print(Blanks(i), self.opts.cpuWait(self.opts), "", self.pinfix(o.args, ", "), ";\n"),

    dma_fence := (self,o,i,is) >> Print(Blanks(i), self.opts.dmaFence(self.opts), "();\n"),

    dma_load := (self,o,i,is) >> Print(Blanks(i), self.opts.dmaLoad(self.opts),
        "(", self(o.loc,i,is), ", ", self(o.exp,i,is), ", ", self(o.size,i,is), ");\n"),

    dma_store := (self,o,i,is) >> Print(Blanks(i), self.opts.dmaStore(self.opts),
        "(", self(o.loc,i,is), ", ", self(o.exp,i,is), ", ", self(o.size,i,is), ");\n"),

    par_exec := (self,o,i,is) >> Print(Blanks(i), "parallel {\n", DoForAll(o.cmds, c -> self(c, i+is, is)), Blanks(i), "}\n"),

    decl := meth(self,o,i,is)
        local arrays, memarrays, scratcharrays, romarrays, other, l, arri, myMem;
        [arrays, other] := SplitBy(o.vars, x->IsArray(x.t));
        [memarrays, arrays] := SplitBy(arrays, i->IsBound(i.t.qualifiers) and self.opts.memModifier in i.t.qualifiers);
        [scratcharrays, arrays] := SplitBy(arrays, i->IsBound(i.t.qualifiers) and self.opts.scratchModifier in i.t.qualifiers);
        [romarrays, arrays] := SplitBy(arrays, i->IsBound(i.t.qualifiers) and self.opts.romModifier in i.t.qualifiers);

        if Length(arrays) > 0 then
            DoForAll(arrays, v -> Print(Blanks(i), self.opts.arrayBufModifier, " ", self.declare(v.t, v, i, is), ";\n"));
        fi;
        if Length(memarrays) > 0 then
            DoForAll(memarrays, v -> Print(Blanks(i), self.opts.arrayBufModifier, " ", self.opts.memModifier, " ", self.declare(v.t, v, i, is), ";\n"));
        fi;
        if Length(scratcharrays) > 0 then
            DoForAll(scratcharrays, v -> Print(Blanks(i), self.opts.arrayBufModifier, " ", self.opts.scratchModifier, " ", self.declare(v.t, v, i, is),";\n"));
        fi;
        if Length(romarrays) > 0 then
            DoForAll(romarrays, v -> Print(Blanks(i), self.opts.arrayBufModifier, " ", self.opts.romModifier, " ", self.declare(v.t, v, i, is), ";\n"));
        fi;

        if (Length(other)>0) then
            other:=SortRecordList(other,x->x.t);
            for l in other do
               Sort(l, (a,b)->a.id < b.id);
               Print(Blanks(i), self.declare(l[1].t, l, i, is), ";\n");
            od;
        fi;

        self(o.cmd, i, is);

        #Pop arena for this decl
        if IsBound(self.opts.useMemoryArena) and self.opts.useMemoryArena and Length(arrays) > 0 and arrays[1].id[1] <> 'D' then
          myMem := 0;
          for arri in arrays do
             # Account for vector allocations in memory arena (which is scalar)
             myMem := myMem + (arri.t.size * When(IsBound(arri.t.t) and ObjId(arri.t.t)=TVect, arri.t.t.size, 1));
          od;
          if ObjId(myMem) = Value then myMem := myMem.v; fi;
          Print(Blanks(i));
          Print("arenalevel += ", myMem, ";\n" );
        fi;
    end,

    swp_loop := (self, o, i, is) >> let(v := o.var, lo := o.range[1], hi := Last(o.range),
        Print(When(IsBound(self.opts.looppragma), self.opts.looppragma(o,i,is)),
          Blanks(i), "for(int ", v, " = ", lo, "; ", v, " <= ", hi, "; ", v, "++) { // SWP loop\n",
          self(o.cmd,i+is,is),
          Blanks(i), "}\n")),
));


SplitString("Hello,How,Are,You,Today", ",");
# [ "Hello", "How", "Are", "You", "Today" ]

JoinStringsWithSeparator(last, ".");
# "Hello.How.Are.You.Today"


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

################################################
#   propagate VTensor into products and sums
_VConsRightBlk := [BlockVPerm, VPerm, VDiag_x_I, VDiag, VRCDiag, RCVDiagSplit, RCVDiag, VGath, VGath_u, VGath_sv, VGath_pc, IxVGath_pc, VGath_dup, VPrm_x_I, Conj, ConjL, ConjR, ConjLR, RCVGath_sv, FormatPrm];
_VConsLeftBlk  := [BlockVPerm, VPerm, VDiag_x_I, VDiag, VRCDiag, RCVDiagSplit, RCVDiag, VScat, VScatAcc, VScatAcc_u, VScat_u, VScat_sv, VScat_svAcc, VScat_pc, IxVScat_pc, VScat_pcAcc, VPrm_x_I, Conj, ConjL, ConjR, ConjLR, RCVScat_sv, FormatPrm];

# !!!! do not put VPerm in here - VPerms CANNOT be pulled into ISums!!!!
_VConsDiag := [ VDiag_x_I, VDiag, VRCDiag, RCVDiag, RCVDiagSplit ];

_VConsRightNoDiag := [VGath, VGath_u, VGath_sv, VPrm_x_I];
_VConsLeftNoDiag  := [VScat, VScat_u, VScat_sv, VPrm_x_I, VScatAcc, VScatAcc_u, VScat_svAcc, VScat_pcAcc, VDiag_x_I];

_VConsRight := _VConsRightNoDiag :: _VConsDiag :: [IxVGath_pc ];
_VConsLeft  := _VConsLeftNoDiag :: _VConsDiag :: [IxVScat_pc ];

# things to not pull into Vcontainer:
_VContDontPullIn := [VContainer, Cross, Cvt, TCvt, RC, BB, ISum, SUM, RecursStep];

_PullInLeft  := [RecursStep, Grp, BB, SUM, Buf, ISum, Data, COND, NeedInterleavedComplex];
_PullInRight := _PullInLeft :: [SUMAcc, ISumAcc];

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

 VecPullInLeft := ARule( Compose, [ @(1, _VConsLeft), @(2, _PullInLeft :: [NoDiagPullinRight]) ],
  e -> [ CopyFields(@(2).val, rec(
             _children :=  List(@(2).val._children, c -> @(1).val * c),
             dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

 VecPullInRight := ARule( Compose, [ @(1, _PullInRight :: [NoDiagPullinLeft]), @(2, _VConsRight) ],
     e -> [ CopyFields(@(1).val, rec(
                _children := List(@(1).val._children, c -> c * @(2).val),
                dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

 VecPullInLeftNoDiag := ARule( Compose, [ @(1, _VConsLeftNoDiag), @(2, [NoDiagPullinLeft, NoDiagPullin]) ],
  e -> [ CopyFields(@(2).val, rec(
             _children :=  List(@(2).val._children, c -> @(1).val * c),
             dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

 VecPullInRightNoDiag := ARule( Compose, [ @(1, [NoDiagPullinRight, NoDiagPullin]), @(2, _VConsRightNoDiag) ],
     e -> [ CopyFields(@(1).val, rec(
                _children := List(@(1).val._children, c -> c * @(2).val),
                dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

#######################################
    VTensorXXX := Rule([@(1, VTensor), @(2, [SUM, SUMAcc, Compose, BB, Buf, Inplace, RecursStep, ISum, ISumAcc, ISumLS, Data, NeedInterleavedComplex, SMPBarrier, SMPSum])],
    e -> let(s := @(2).val, CopyFields(s,
         rec(_children := List(s.children(), c->VTensor(c, @(1).val.vlen)),
             dimensions := @(1).val.dimensions)))),

    VTensorIndXXX := Rule([@(1, VTensorInd), @(2, [SUM, SUMAcc, Compose, BB, Buf, Inplace, RecursStep, ISum, ISumAcc, ISumLS, Data, NeedInterleavedComplex, SMPBarrier, SMPSum]), @(3)],
    e -> let(s := @(2).val, CopyFields(s,
         rec(_children := List(s.children(), c->VTensorInd(c, @(3).val)),
             dimensions := @(1).val.dimensions)))),

    # Free variables in breakdown rules seems to be broken...
    VTensorInd_VTensor := Rule([@(1, VTensorInd), @(2).cond(e->ObjId(e)<>RTWrap), @(3).cond(e->not(e in @(2).val.free()))],
        e -> VTensor(@(2).val, @(3).val.range)),

#(s) -> SubstTopDownNR(RulesRC(s), @@(1, RCDiag, (x, cx) -> IsBound(cx.VRCLR) and cx.VRCLR <> [  ] or x.element.range() = TComplex), (x) -> x.toloop().sums())

    # NOTE (handle xofs and yofs correctly)
    MergeRS := ARule(Compose, [@(1,RecursStep), @(2,RecursStep)],
    e -> [ RecursStep(0,0,@(1).val.child(1)*@(2).val.child(1)) ]),

    # NOTE (handle xofs and yofs correctly)
    # V * OP(a) -> OP(V*a), including the case V=BlockVPerm
    XXX_VConsBlk := ARule(Compose, [@(1,[RecursStep, Buf, Inplace, BB, NeedInterleavedComplex]), @(2,_VConsRightBlk)],
    e -> [ ObjId(@(1).val)(@(1).val.child(1)*@(2).val) ]),
    XXX_VConsBlk_VCont := ARule(Compose, [@(1,[RecursStep, Buf, Inplace, BB, NeedInterleavedComplex]), [@(2, VContainer), @(3,_VConsRightBlk)]],
    e -> [ ObjId(@(1).val)(@(1).val.child(1)*@(2).val) ]),

    VConsBlk_XXX := ARule(Compose, [@(1,_VConsLeftBlk), @(2, [RecursStep, Buf, Inplace, BB, NeedInterleavedComplex])],
    e -> [ ObjId(@(2).val)(@(1).val*@(2).val.child(1)) ]),
    VConsBlk_VCont_XXX := ARule(Compose, [[@(1, VContainer), @(3,_VConsLeftBlk)], @(2, [RecursStep, Buf, Inplace, BB, NeedInterleavedComplex])],
    e -> [ ObjId(@(2).val)(@(1).val*@(2).val.child(1)) ]),

    VConsBlk_XXX := ARule(Compose, [@(1,_VConsLeftBlk), @(2, [RecursStep, Buf, Inplace, BB, NeedInterleavedComplex])],
    e -> [ ObjId(@(2).val)(@(1).val*@(2).val.child(1)) ]),

    VContainer_XXX := ARule(Compose, [@(1,VContainer), @(2).cond(x -> not (ObjId(x) in _VContDontPullIn))],
        e -> [ ObjId(@(1).val)(@(1).val.child(1)*@(2).val, @(1).val.isa) ]),

    XXX_VContainer := ARule(Compose, [@(1).cond(x -> not (ObjId(x) in _VContDontPullIn)), @(2, VContainer)],
        e -> [ ObjId(@(2).val)(@(1).val*@(2).val.child(1), @(2).val.isa) ]),

    Compose_VContainers := ARule(Compose, [ @(1, VContainer), @(2, VContainer, x -> x.isa=@(1).val.isa and x.isa.isCplx()=@(1).val.isa.isCplx())],
        e -> [VContainer(@(1).val.child(1) * @(2).val.child(1), @(1).val.isa)]),

    ISum_VContainer := Rule( [ISum, [@(1, VContainer), @(2)]],
        e -> VContainer(ISum(e.var, e.domain, @(2).val), @(1).val.isa)),
    IParSeq_VContainer := Rule( [IParSeq, [@(1, VContainer), @(2)]],
        e -> VContainer(IParSeq(e.var, e.domain, e.fb_cnt, @(2).val), @(1).val.isa)),
    SUM_VContainer := Rule( [@(1, SUM), @(2, VContainer, x -> ForAll(@(1).val.children(), a -> ObjId(a)=VContainer)), ...],
        e -> VContainer( ApplyFunc(SUM, List(e.children(), l -> l.child(1))), @(2).val.isa)),
    VContainer_PullFrom := Rule( [@(1, [NoDiagPullin, NoDiagPullinLeft, NoDiagPullinRight, CR, SymSPL]), @(2, VContainer)],
        e -> VContainer( ObjId(@(1).val)(@(2).val.child(1)), @(2).val.isa )),
    BlockVPerm_VContainer := Rule( [BlockVPerm, @(1, VContainer)],
        e -> VContainer( BlockVPerm(e.n, e.vlen, @(2).val.child(1), e.perm), @(1).val.isa )),
    # WrappedVCons_XXX and XXX_WrappedVCons rules duplicate VCons_XXX and XXX_VCons to suck
    # Vconstructs wrapped into VContainer.
    # V * SUM(a,b,...) -> SUM(V*a, V*b, ...), do not suck in BlockVPerm
    WrappedVCons_XXX := ARule(Compose, [ [@(1, VContainer), @(0, _VConsLeft)], @(2, [SUM, SMPSum, SMPBarrier, ISum, Data]) ], e -> let(s:=@(2).val,
    [ CopyFields(s, rec(_children := List(s.children(), c -> @(1).val * c ),
                    dimensions := [@(1).val.dimensions[1], @(2).val.dimensions[2]])) ])),

    XXX_WrappedVCons := ARule(Compose, [ @(1, [SUM, SUMAcc, SMPSum, SMPBarrier, ISum, Data]), [@(2, VContainer), @(0, _VConsRight)]], e -> let(s:=@(1).val,
    [ CopyFields(s, rec(_children := List(s.children(), c -> c * @(2).val ),
                    dimensions := [@(1).val.dimensions[1], @(2).val.dimensions[2]])) ])),

    # V * SUM(a,b,...) -> SUM(V*a, V*b, ...), do not suck in BlockVPerm
    VCons_XXX := ARule(Compose, [ @(1, _VConsLeft), @(2, [SUM, SMPSum, SMPBarrier, ISum, Data]) ], e -> let(s:=@(2).val,
    [ CopyFields(s, rec(_children := List(s.children(), c -> @(1).val * c ),
                    dimensions := [@(1).val.dimensions[1], @(2).val.dimensions[2]])) ])),

    XXX_VCons := ARule(Compose, [ @(1, [SUM, SUMAcc, SMPSum, SMPBarrier, ISum, Data]), @(2, _VConsRight)], e -> let(s:=@(1).val,
    [ CopyFields(s, rec(_children := List(s.children(), c -> c * @(2).val ),
                    dimensions := [@(1).val.dimensions[1], @(2).val.dimensions[2]])) ])),

    COND_VCons := ARule(Compose, [@(1, COND), @(2, _VConsRight)],
        e -> [COND(@(1).val.cond, @(1).val.child(1)*@(2).val, @(1).val.child(2)*@(2).val)]),

    VCons_COND := ARule(Compose, [@(1, _VConsLeft), @(2, COND)],
        e -> [COND(@(2).val.cond, @(1).val*@(2).val.child(1), @(1).val*@(2).val.child(2))]),

    # BlockVPerm * ISumLS
    BlockVPermISum := ARule(Compose,  [ @(1, BlockVPerm), @(2, ISumLS) ],
         e -> [ ISum(@(2).val.var, @(2).val.domain, @(1).val * @(2).val.child(1)) ]),

    ISumBlockVPerm  := ARule(Compose, [ @(1, ISumLS), @(2, BlockVPerm) ],
        e -> [ ISum(@(1).val.var, @(1).val.domain, @(1).val.child(1) * @(2).val) ]),

    # Replace by vector constructs
    VectGath := Rule([@(1, VTensor), @(2, Gath), ...], e->VGath(@(2).val.func, @(1).val.vlen)),
    VectScat := Rule([@(1, VTensor), @(2, Scat), ...], e->VScat(@(2).val.func, @(1).val.vlen)),
    VectScatAcc := Rule([@(1, VTensor), @(2, ScatAcc), ...], e->VScatAcc(@(2).val.func, @(1).val.vlen)),

    # ----------------------
    # Combine Gath/Scat
    #
    ComposeVGathVGath := ARule(Compose, [ @(1, VGath), @(2, [VGath, VGath_u, VGath_dup], e->@(1).val.v=e.v) ], # o 1-> 2->
        e -> [ ObjId(@(2).val)(fCompose(@(2).val.func, @(1).val.func), @(1).val.v) ]),
    # there is a copy of the rule below in autolib, why?
    ComposeVGath_dup_Gath := ARule(Compose, [@(1, VGath_dup), @(2, [Gath, Prm])],
    e -> [ VGath_dup(fCompose(Cond(ObjId(@2.val)=Prm,@(2).val.func,@(2).val.func), @(1).val.func), @(1).val.v) ]),

    ComposeVScatVScat := ARule(Compose, [ @(1, [VScat_u, VScat, VScatAcc, VScatAcc_u]), @(2, VScat, e->@(1).val.v=e.v) ], # <-1 <-2 o
        e -> [ ObjId(@(1).val)(fCompose(@(1).val.func, @(2).val.func), @(1).val.v) ]),

    ComposeVScatVScatAcc := ARule(Compose, [ @(1, [VScat_u, VScat, VScatAcc, VScatAcc_u]), @(2, VScatAcc, e->@(1).val.v=e.v) ], # <-1 <-2 o
        e -> let(scat := Cond(@(1).val _is VScat,   VScatAcc,
		              @(1).val _is VScat_u, VScatAcc_u,
			      @(1).val),
	    [ scat(fCompose(@(1).val.func, @(2).val.func), @(1).val.v) ])),

    ComposeVScatPrm  := ARule(Compose, [@(1, [VScat, VScat_u, VScatAcc, VScatAcc_u]),   @(2, VPrm_x_I)], # 1-> <-2 o
        e -> [ ObjId(@(1).val)(fCompose(@(1).val.func, @(2).val.func.transpose()), @(1).val.v) ]),
    ComposeVGathPrm  := ARule(Compose, [ @(1, VGath), @(2, VPrm_x_I) ], # o 1-> 2->
        e -> [ VGath(fCompose(@(2).val.func, @(1).val.func), @(1).val.v) ]),
    ComposePrmVScat  := ARule(Compose, [ @(1, VPrm_x_I), @(2, [VScat, VScatAcc]) ], # 1-> <-2 o
        e -> [ ObjId(@(2).val)(fCompose(@(1).val.func.transpose(), @(2).val.func), @(2).val.v) ]),
    ComposePrmVGath  := ARule(Compose, [ @(1, VPrm_x_I), @(2, [VGath, VGath_u])], # o 1-> 2->
        e -> [ ObjId(@(2).val)(fCompose(@(2).val.func, @(1).val.func), @(2).val.v) ]),

    ComposeVTensorPrm  := ARule(Compose, [@(1, VTensor),   @(2, Prm)],
        e -> [ @(1).val, VGath_sv(@(2).val.func, @(1).val.vlen, 1) ]),
    ComposePrmVTensor  := ARule(Compose, [ @(1, Prm), @(2, VTensor) ],
        e -> [  VScat_sv(@(1).val.func.transpose(), @(2).val.vlen, 1), @(2).val ]),

## NOTE: These rules seem broken. There seems to be an assumption on the size of the Scatter. look at Compose_IxVGath_pc__Gath which was fixed for BG/Q 3D FFT
    ComposeIxVGath_pc__Prm  := ARule(Compose, [ @(1, IxVGath_pc), @(2, [Prm, DelayedPrm])], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(@(2).val.func, g.k, g.v) ])),
    Compose_IxVGath_pc__Gath  := ARule(Compose, [ @(1, IxVGath_pc), @(2, Gath)], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(fCompose(@(2).val.func, fTensor(fId(g.k), fAdd(g.N, g.n, g.ofs))), g.k, g.v) ])),
    Compose_IxVGath_pc__VGath_sv  := ARule(Compose, [ @(1, IxVGath_pc), @(2, VGath_sv)], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(fTensor(@(2).val.func, fId(@(2).val.sv)), g.k, g.v) ])),
    Compose_IxVGath_pc__VGath  := ARule(Compose, [ @(1, IxVGath_pc), @(2, VGath)], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(fTensor(@(2).val.func, fId(g.v)), g.k, g.v) ])),
    Compose_IxRCVGath_pc__RCGath  := ARule(Compose, [ @(1, IxRCVGath_pc), @(2, VGath_sv, e->e.sv=2)], # o 1-> 2->
        e -> let(g:=@(1).val, [ RCVStretchGath(@(2).val.func, g.k, g.v) ])),

    Compose_VGath__IxVGath_pc := ARule(Compose, [ @(1, VGath, e->e.func.domain()<=2), @(2, IxVGath_pc, e->e.n <= e.v)],
        e -> let(g := @(1).val, gpc := @(2).val,
            Cond(g.func.domain()=1,
                [ VGath_pc(gpc.k*gpc.N, gpc.n, fCompose(fTensor(fId(gpc.k), fAdd(gpc.N, gpc.n, gpc.ofs)), fTensor(g.func, fId(gpc.n))).at(0), gpc.v) ],
                [ SUM(
                    VScat(fBase(V(2),V(0)), g.v) * VGath_pc(gpc.k*gpc.N, gpc.n, fCompose(fTensor(fId(gpc.k), fAdd(gpc.N, gpc.n, gpc.ofs)), fTensor(fCompose(g.func, fBase(V(2),V(0))), fId(gpc.n))).at(0), gpc.v),
                    VScat(fBase(V(2),V(1)), g.v) * VGath_pc(gpc.k*gpc.N, gpc.n, fCompose(fTensor(fId(gpc.k), fAdd(gpc.N, gpc.n, gpc.ofs)), fTensor(fCompose(g.func, fBase(V(2),V(1))), fId(gpc.n))).at(0), gpc.v))
                ]
        ))),

## NOTE: These rules seem broken. There seems to be an assumption on the size of the Scatter. look at Compose_IxVGath_pc__Gath which was fixed for BG/Q 3D FFT
    ComposePrmIxVScat_pc  := ARule(Compose, [ @(1, [Prm, DelayedPrm]), @(2, IxVScat_pc) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(@(1).val.func.transpose(), s.k, s.v) ])),
    Compose_Scat__IxVScat_pc  := ARule(Compose, [ @(1, Scat), @(2, IxVScat_pc) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(fCompose(@(1).val.func, fTensor(fId(s.k), fAdd(s.N, s.n, s.ofs))), s.k, s.v) ])),
    Compose_VScat_sv__IxVScat_pc  := ARule(Compose, [ @(1, VScat_sv), @(2, IxVScat_pc) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(fTensor(@(1).val.func, fId(@(1).val.sv)), s.k, s.v) ])),
    Compose_VScat__IxVScat_pc  := ARule(Compose, [ @(1, VScat), @(2, IxVScat_pc) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(fTensor(@(1).val.func, fId(s.v)), s.k, s.v) ])),
    Compose_RCScat__IxRCVScat_pc  := ARule(Compose, [ @(1, VScat_sv, e->e.sv=2), @(2, IxRCVScat_pc) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ RCVStretchScat(@(1).val.func, s.k, s.v) ])),

    Compose_IxVScat_pc__VScat := ARule(Compose, [ @(1, IxVScat_pc, e->e.n <= e.v), @(2, VScat, e->e.func.domain()<=2)],
        e -> let(s := @(2).val, spc := @(1).val,
            Cond(s.func.domain()=1,
                [ VScat_pc(spc.k*spc.N, spc.n, fCompose(fTensor(fId(spc.k), fAdd(spc.N, spc.n, spc.ofs)), fTensor(s.func, fId(spc.n))).at(0), spc.v) ],
                [ SUM(
                    VScat_pc(spc.k*spc.N, spc.n, fCompose(fTensor(fId(spc.k), fAdd(spc.N, spc.n, spc.ofs)), fTensor(fCompose(s.func, fBase(V(2),V(0))), fId(spc.n))).at(0), spc.v) * VGath(fBase(V(2),V(0)), s.v),
                    VScat_pc(spc.k*spc.N, spc.n, fCompose(fTensor(fId(spc.k), fAdd(spc.N, spc.n, spc.ofs)), fTensor(fCompose(s.func, fBase(V(2),V(1))), fId(spc.n))).at(0), spc.v) * VGath(fBase(V(2),V(1)), s.v))
                ]
        ))),

    ComposeVStretchGath__Prm  := ARule(Compose, [ @(1, VStretchGath), @(2, [Prm,DelayedPrm])], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(fCompose(@(2).val.func, g.func), g.part, g.v) ])),
    Compose_VStretchGath__Gath  := ARule(Compose, [ @(1, VStretchGath), @(2, Gath)], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(fCompose(@(2).val.func, g.func), g.part, g.v) ])),
    Compose_VStretchGath__VGath  := ARule(Compose, [ @(1, VStretchGath), @(2, VGath)], # o 1-> 2->
        e -> let(g:=@(1).val, [ VStretchGath(fCompose(fTensor(@(2).val.func, fId(g.v)), g.func), g.part, g.v) ])),

    ComposePrmVStretchScat  := ARule(Compose, [ @(1, [Prm,DelayedPrm]), @(2, VStretchScat) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(fCompose(@(1).val.func.transpose(), s.func), s.part, s.v) ])),
    Compose_Scat__VStretchScat  := ARule(Compose, [ @(1, Scat), @(2, VStretchScat) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(fCompose(@(1).val.func, s.func), s.part, s.v) ])),
    Compose_VScat__VStretchScat  := ARule(Compose, [ @(1, VScat), @(2, VStretchScat) ], # 1-> <-2 o
        e -> let(s:=@(2).val, [ VStretchScat(fCompose(fTensor(@(1).val.func, fId(s.v)), s.func), s.part, s.v) ])),

    Drop_GathScat := ARule(Compose, [ @(1), @(2, VGath),  @(3, VScat, x -> x.transpose()=@(2).val), @(4)],
        e -> [@(1).val, @(4).val]),
# uncombinable gather/scatter CANNOT be pulled in!!!
# NOTE: only pull till BB(), then stop -> new rewriting stage _after_ MarkBB
#    XXX_ISum := ARule(Compose,  [ @(1, [IxVScat_pc, IxRCVScat_pc, VStretchScat, RCVStretchScat]), @(2, ISum) ],
#         e -> [ ObjId(@(2).val)(@(2).val.var, @(2).val.domain, @(1).val * @(2).val.child(1)).attrs(@(2).val) ]),
#
#    ISum_XXX  := ARule(Compose, [ @(1, [ISum, ISumAcc]), @(2, [IxVGath_pc, IxRCVGath_pc, VStretchGath, RCVStretchGath]) ],
#        e -> [ ObjId(@(1).val)(@(1).val.var, @(1).val.domain, @(1).val.child(1) * @(2).val).attrs(@(1).val) ]),

# uncombinable gather/scatter CANNOT be pulled in!!!
# NOTE: only pull till BB(), then stop -> new rewriting stage _after_ MarkBB
#    VScatSUM := ARule(Compose, [ @(1, [IxVScat_pc, IxRCVScat_pc, VStretchScat, RCVStretchScat]), @(2, SUM) ],
#     e -> [ ApplyFunc(ObjId(@(2).val),
#                  List(@(2).val.children(), c -> @(1).val * c)) ]),
#
#    VGathSUM  := ARule(Compose, [ @(1, SUM), @(2, [IxVGath_pc, IxRCVGath_pc, VStretchGath, RCVStretchGath])],
#     e -> [ ApplyFunc(ObjId(@(1).val),
#                  List(@(1).val.children(), c -> c * @(2).val)) ]),

################################################
# NOTE!!!!!!!!!!!!!!!!!!
# GUARD MISSING: gather/scatter must be f x fId(v^2)
#    ComposeBlockVPermVScat := ARule(Compose,[ @(1, BlockVPerm), @(2, VScat)],
#        e->[@2.val, BlockVPerm(@2.val.dimensions[2]/@1.val.child(1).dims()[1], @1.val.vlen, @1.val.child(1), @1.val.perm)]
#    ),
#    ComposeVGathBlockVPerm := ARule(Compose,[ @(1, VGath), @(2, BlockVPerm)],
#        e->[BlockVPerm(@1.val.dimensions[1]/@2.val.child(1).dims()[2], @2.val.vlen, @2.val.child(1), @2.val.perm), @1.val ]
#    ),

    ScatH_VScat_pc := ARule(Compose, [ [ @(1, [Scat,ScatAcc]), fId ], @(2, [VScat_pc, VScat_pcAcc]) ],
	e -> let(s   := @(2).val,
	         oid := Cond((@(1).val _is Scat) and (@(2).val _is VScat_pc), VScat_pc, VScat_pcAcc),
	         [ oid(s.N, s.n, s.ofs, s.v) ])),

    ScatId_VScat_pc := ARule(Compose, [ [ @(1, [Scat,ScatAcc]), [ @(2,H), @, @, @, 1 ] ], @(3, [VScat_pc, VScat_pcAcc]) ],
	e -> let(h   := @(2).val,
	         s   := @(3).val,
	         oid := Cond((@(1).val _is Scat) and (@(3).val _is VScat_pc), VScat_pc, VScat_pcAcc),
	         [ oid(h.params[1], s.n, s.ofs + h.params[3], s.v) ])),

    VGath_pc__toVGath := Rule(@(1, VGath_pc, e->let(v := e.v, (e.N mod v = 0) and (e.n mod v = 0) and (e.ofs mod v = 0))),
        e->let(g := @(1).val, v := g.v, VGath(fAdd(g.N/v, g.n/v, g.ofs/v), v))),

    VScat_pc__toVScat := Rule(@(1, VScat_pc, e->let(v := e.v, (e.N mod v = 0) and (e.n mod v = 0) and (e.ofs mod v = 0))),
        e->let(s := @(1).val, v := s.v, VScat(fAdd(s.N/v, s.n/v, s.ofs/v), v))),

    VScat_pcAcc__toVScatAcc := Rule(@(1, VScat_pcAcc, e->let(v := e.v, (e.N mod v = 0) and (e.n mod v = 0) and (e.ofs mod v = 0))),
        e->let(s := @(1).val, v := s.v, VScatAcc(fAdd(s.N/v, s.n/v, s.ofs/v), v)))

));

Class(RulesTerm, RuleSet);
RewriteRules(RulesTerm, rec(
    ISumLS_Term := Rule(@(1,ISumLS), e->ISum(@(1).val.var, @(1).val.domain, @(1).val.child(1)).attrs(@(1).val)),
    BlockVPerm_Term := Rule(@(1,[BlockVPerm, BlockVPerm2]), e-> Tensor(I(@1.val.n), @1.val._children[1]).sums()),
#   BROKEN RULES; to be redone for loop code
#    IxVGath_pc_term := Rule(@(1,IxVGath_pc, e->e.k>1),
#        e->let(i := Ind(), k:= @1.val.k, v:=@1.val.v,
#            ISum(i, k,
#                VScat(fTensor(fBase(k,i), fId(@1.val.nv/v)), v) *
#                IxVGath_pc(1, k*@1.val.N, @1.val.n, add(mul(i,k), @1.val.ofs), v)
#            ))),
#    IxVScat_pc_term := Rule(@(1,IxVScat_pc, e->e.k>1),
#        e->let(i := Ind(), k:= @1.val.k, v:=@1.val.v,
#            ISum(i, k,
#                IxVScat_pc(1, k*@1.val.N, @1.val.n, add(mul(i,k), @1.val.ofs), v) *
#                VGath(fTensor(fBase(k,i), fId(@1.val.nv/v)), v)
#            ))),
#
    term_PushLR := Rule(@(1, PushLR), e->e.child(1).sums()),

));

Class(RulesTermGrp, RuleSet);
RewriteRules(RulesTermGrp, rec(
    Grp_Term := Rule(@(1,Grp), e-> @1.val._children[1])
));

Class(RulesKickout, RuleSet);
RewriteRules(RulesKickout, rec(
    IxVGath_pc__IxVScat_pc_kickout := ARule(Compose, [@(1, IxVGath_pc), @(2, IxVScat_pc, g->let(s:=@(1).val, s.k=g.k and s.n=g.n and s.N=g.N and s.ofs=g.ofs and s.v=g.v))],
            e->let(g:=@(1).val, [VGath(fId(Rows(g)/g.v), g.v), VScat(fId(Rows(g)/g.v), g.v)])),

    IxVScat_VScat_kickout := Rule(@(1,IxVScat_pc, s-> IsInt(s.n/s.v) and IsInt(s.N/s.v) and IsInt(s.ofs/s.v)),
            e->let(s:=@(1).val, v:=s.v, VScat(fTensor(fId(s.k), fAdd(s.N/v, s.n/v, s.ofs/v)), v))),

    IxVGath_VGath_kickout := Rule(@(1,IxVGath_pc, s-> IsInt(s.n/s.v) and IsInt(s.N/s.v) and IsInt(s.ofs/s.v)),
            e->let(s:=@(1).val, v:=s.v, VGath(fTensor(fId(s.k), fAdd(s.N/v, s.n/v, s.ofs/v)), v))),
));

Class(TerminateSymSPL, RuleSet);
RewriteRules(TerminateSymSPL, rec(
    terminateSymSPL := Rule(@(1, SymSPL), e->@(1).val.child(1))
));

Class(TerminateDPrm, RuleSet);
RewriteRules(TerminateSymSPL, rec(
    terminateDPrm := Rule(@(1, DelayedPrm), e->Prm(@(1).val.func))
));