Class(Dot_X, BaseOperation, rec(
  __call__ := (self, sr, x_n, s1, s2, range) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    start1 := s1,
    start2 := s2,
    range := range,
    x_n := x_n,
    _children := [range]
  ))),
  dims := self >> [self.x_n, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.start1, ", ", self.start2, ", ", self.range, ")"
  )
));

Class(Accum_X, BaseOperation, rec(
  __call__ := (self, sr, x_n, var, st, range, func) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    var := var,
    start := st,
    range := range,
    func := self >> self._children[1],
    x_n := x_n,
    _children := [func, range]
  ))),
  dims := self >> [self.x_n, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.var, ", ", self.start, ", ", self.range, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
	)
));

Class(Scat_E, BaseOperation, rec(
  __call__ := (self, sr, x_n, v1, v2, range, svar, func) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    v1 := v1,
    v2 := v2,
    range := range,
    svar := svar,
    func := self >> self._children[1],
    x_n := x_n,
    _children := [func, range]
  ))),
  dims := self >> [self.x_n, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.v1, ", ", self.v2, ", ", self.range, ", ", self.svar, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(SMP_Scat_E, BaseOperation, rec(
  __call__ := (self, sr, x_n, v1, v2, range, svar, func, nthreads) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    v1 := v1,
    v2 := v2,
    range := range,
    svar := svar,
    func := self >> self._children[1],
    x_n := x_n,
    nthreads := nthreads,
    _children := [func, range]
  ))),
  dims := self >> [self.x_n, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.v1, ", ", self.v2, ", ", self.range, ", ", self.svar, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(CheckEdgesUpdate, BaseOperation, rec(
  __call__ := (self, edges, var, func) >> SPL(WithBases(self, rec(
    edges := edges,
    var := var,
    func := self >> self._children[1],
    _children := [func]
  ))),
  dims := self >> [self.edges, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.edges, ", ", self.var, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(PruneEdges, BaseOperation, rec(
  __call__ := (self, idx, range, var, x_n, s, k) >> SPL(WithBases(self, rec(
    idx := idx,
    range := range,
    var := var,
    x_n := x_n,
    s := s,
    k := k,
    _children := [range, k]
  ))),
  dims := self >> [self.x_n, V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.idx, ", ", self.range, ", ", self.var, ", ", self.s, ", ", self.k, ")"
  )
));

Class(Accum, BaseOperation, rec(
  __call__ := (self, sr, var, range_min, range_max, func) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    var := var,
    range_min := range_min,
    range_max := range_max,
    func := self >> self._children[1],
    _children := [func, range_min, range_max]
  ))),
  dims := self >> self._children[1].dims(),
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.var, ", ", self.range_min, ", ", self.range_max, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(SMP_Accum, BaseOperation, rec(
  __call__ := (self, sr, var, range_min, range_max, func, nthreads) >> SPL(WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    var := var,
    range_min := range_min,
    range_max := range_max,
    func := self >> self._children[1],
    nthreads := nthreads,
    _children := [func, range_min, range_max]
  ))),
  dims := self >> self._children[1].dims(),
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.sr, ", ", self.var, ", ", self.range_min, ", ", self.range_max, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(Assign_Ptr, BaseOperation, rec(
  __call__ := (self, func) >> SPL(WithBases(self, rec(
    func := self >> self._children[1],
    _children := [func]
  ))),
  dims := self >> self._children[1].dims(),
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(Scale_Pt, BaseOperation, rec(
  __call__ := (self, scalar, func) >> SPL(WithBases(self, rec(
    scalar := scalar,
    func := self >> self._children[1],
    _children := [func]
  ))),
  dims := self >> self._children[1].dims(),
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.scalar, ",\n",
        Blanks(i+is), self.func().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(IterateIndefinite, BaseOperation, rec(
  __call__ := (self, op1, op2) >> SPL(WithBases(self, rec(
    op1 := self >> self._children[1],
    op2 := self >> self._children[2],
    _children := [op1, op2]
  ))),
  dims := self >> [V(1), V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(\n",
        Blanks(i+is), self.op1().print(i+is, is), "\n",
        Blanks(i+is), self.op2().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(KTrussWrap, BaseOperation, rec(
  __call__ := (self, s, body) >> SPL(WithBases(self, rec(
    s := s,
    body := self >> self._children[1],
    _children := [body]
  ))),
  dims := self >> [V(1), V(1)],
  from_rChildren := (self, rch) >> CopyFields(self, rec(_children := rch)),
  doNotMarkBB := true,
  print := (self, i, is) >> Print(
        self.name, "(", self.s, "\n",
        Blanks(i+is), self.body().print(i+is, is), "\n",
        Blanks(i), ")"
  )
));

Class(loopw, Command, rec(
  __call__ := (self, cond, cmd) >> WithBases(self, rec(
    cond := toExpArg(cond),
    cmd := Checked(IsCommand(cmd), cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.cond, self.cmd],
  rSetChild := rSetChildFields("cond", "cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.cond, ",\n",
        Blanks(i+si), self.cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(loopf, Command, rec(
  __call__ := (self, var, range_min, range_max, cmd) >> WithBases(self, rec(
    var := var,
    range_min := range_min,
    range_max := range_max,
    cmd := Checked(IsCommand(cmd), cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.var, self.range_min, self.range_max, self.cmd],
  rSetChild := rSetChildFields("var", "range_min", "range_max", "cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.var, ", ", self.range_min, ", ", self.range_max, ",\n",
        Blanks(i+si), self.cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(smp_reduct_loopf, Command, rec(
  __call__ := (self, sr, var, range_min, range_max, y, nthreads, cmd) >> WithBases(self, rec(
    sr := sr,
    var := var,
    range_min := range_min,
    range_max := range_max,
    y := y,
    nthreads := nthreads,
    cmd := Checked(IsCommand(cmd), cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.sr, self.var, self.range_min, self.range_max, self.y, self.nthreads, self.cmd],
  rSetChild := rSetChildFields("sr", "var", "range_min", "range_max", "y", "nthreads", "cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.var, ", ", self.range_min, ", ", self.range_max, ", ", self.nthreads, ",\n",
        Blanks(i+si), self.cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(smp_loopf, Command, rec(
  __call__ := (self, var, range_min, range_max, nthreads, cmd) >> WithBases(self, rec(
    var := var,
    range_min := range_min,
    range_max := range_max,
    nthreads := nthreads,
    cmd := Checked(IsCommand(cmd), cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.var, self.range_min, self.range_max, self.nthreads, self.cmd],
  rSetChild := rSetChildFields("var", "range_min", "range_max", "nthreads", "cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.var, ", ", self.range_min, ", ", self.range_max, ", ", self.nthreads, ",\n",
        Blanks(i+si), self.cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(if1, Command, rec(
  __call__ := (self, if_cond, if_cmd) >> WithBases(self, rec(
    if_cond := toExpArg(if_cond),
    if_cmd := Checked(IsCommand(if_cmd), if_cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.if_cond, self.if_cmd],
  rSetChild := rSetChildFields("if_cond", "if_cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.if_cond, ",\n",
        Blanks(i+si), self.if_cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(if2, Command, rec(
  __call__ := (self, if_cond, if_cmd, ei_cond, ei_cmd, else_cmd) >> WithBases(self, rec(
    if_cond := toExpArg(if_cond),
    if_cmd := Checked(IsCommand(if_cmd), if_cmd),
    else_cmd := Checked(IsCommand(else_cmd), else_cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.if_cond, self.if_cmd, self.else_cmd],
  rSetChild := rSetChildFields("if_cond", "if_cmd", "else_cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.if_cond, ",\n",
        Blanks(i+si), self.if_cmd.print(i+si, si), ",\n",
        Blanks(i+si), self.else_cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(if3, Command, rec(
  __call__ := (self, if_cond, if_cmd, ei_cond, ei_cmd, else_cmd) >> WithBases(self, rec(
    if_cond := toExpArg(if_cond),
    if_cmd := Checked(IsCommand(if_cmd), if_cmd),
    ei_cond := toExpArg(ei_cond),
    ei_cmd := Checked(IsCommand(ei_cmd), ei_cmd),
    else_cmd := Checked(IsCommand(else_cmd), else_cmd),
    operations := CmdOps
  )),
  rChildren := self >> [self.if_cond, self.if_cmd, self.ei_cond, self.ei_cmd, self.else_cmd],
  rSetChild := rSetChildFields("if_cond", "if_cmd", "ei_cond", "ei_cmd", "else_cmd"),
  print := (self, i, si) >> Print(self.__name__, "(", self.if_cond, ",\n",
        Blanks(i+si), self.if_cmd.print(i+si, si), ",\n",
        Blanks(i+si), self.ei_cond, ",\n",
        Blanks(i+si), self.ei_cmd.print(i+si, si), ",\n",
        Blanks(i+si), self.else_cmd.print(i+si, si), "\n",
        Blanks(i), ")"
  )
));

Class(break_ic, Command, rec(
  __call__ := (self) >> WithBases(self, rec(
    operations := CmdOps
  )),
  rChildren := self >> [],
  rSetChild := rSetChildFields(),
  print := (self, i, si) >> Print(self.__name__, "()")
));

CUnparser.loopw := (self,o,i,is) >> Print(Blanks(i),
    "while (", self(o.cond,i,is), ") {\n",
    self(o.cmd,i+is,is), Blanks(i), "}\n");

CUnparser.loopf := (self,o,i,is) >> Print(Blanks(i),
    "for (int ", o.var, " = ", self(o.range_min,i,is), "; ", o.var, " < ", self(o.range_max,i,is), "; ", o.var, "++) {\n",
    self(o.cmd,i+is,is), Blanks(i), "}\n");

CUnparser.smp_reduct_loopf := (self,o,i,is) >> Print(Blanks(i),
    "#pragma omp parallel for schedule(dynamic) reduction(", o.sr.OpenMP_sum, ":", o.y, ") num_threads(", o.nthreads, ")\n",
    Blanks(i), "for (int ", o.var, " = ", self(o.range_min,i,is), "; ", o.var, " < ", self(o.range_max,i,is), "; ", o.var, "++) {\n",
    self(o.cmd,i+is,is), Blanks(i), "}\n");

CUnparser.smp_loopf := (self,o,i,is) >> Print(Blanks(i),
    "#pragma omp parallel for schedule(dynamic) num_threads(", o.nthreads, ")\n",
    Blanks(i), "for (int ", o.var, " = ", self(o.range_min,i,is), "; ", o.var, " < ", self(o.range_max,i,is), "; ", o.var, "++) {\n",
    self(o.cmd,i+is,is), Blanks(i), "}\n");

CUnparser.if1 := (self,o,i,is) >> Print(Blanks(i),
    "if (", self(o.if_cond,i,is), ") {\n", self(o.if_cmd,i+is,is), Blanks(i), "}\n");

CUnparser.if2 := (self,o,i,is) >> Print(Blanks(i),
    "if (", self(o.if_cond,i,is), ") {\n", self(o.if_cmd,i+is,is), Blanks(i), "}",
    " else {\n", self(o.else_cmd,i+is,is), Blanks(i), "}\n");

CUnparser.if3 := (self,o,i,is) >> Print(Blanks(i),
    "if (", self(o.if_cond,i,is), ") {\n", self(o.if_cmd,i+is,is), Blanks(i), "}",
    " else if (", self(o.ei_cond,i,is), ") {\n", self(o.ei_cmd,i+is,is), Blanks(i), "}",
    " else {\n", self(o.else_cmd,i+is,is), Blanks(i), "}\n");

CUnparser.break_ic := (self,o,i,is) >> Print(Blanks(i),
    "break;\n");

Class(VMV_X, TaggedNonTerminal, rec(
  __call__ := (self, sr, s1, sm, s2, d1, d2, x_n) >> WithBases(self, rec(
    sr := Checked(IsSemiring(sr), sr),
    start1 := s1,
    startm := sm,
    start2 := s2,
    d1 := d1,
    d2 := d2,
    x_n := x_n,
    params := []
  )),
  isReal := self >> true,
  dims := self >> [self.x_n, V(1)]
));

Class(TriangleCount, TaggedNonTerminal, rec(
  abbrevs := [ (n) -> [n] ],
  isReal := self >> true,
  dims := self >> [self.params[1], self.params[1]]
));

Class(KTruss, TaggedNonTerminal, rec(
  abbrevs := [ (n, m, k) -> [n, m, k] ],
  isReal := self >> true,
  dims := self >> [self.params[1], self.params[1]]
));

Class(TriangleCountEdges, TaggedNonTerminal, rec(
  abbrevs := [ (n, m, s) -> [n, m, s] ],
  isReal := self >> true,
  dims := self >> [self.params[1], self.params[1]]
));

Class(KTrussPrune, TaggedNonTerminal, rec(
  abbrevs := [ (n, m, k, s) -> [n, m, k, s] ],
  isReal := self >> true,
  dims := self >> [self.params[1], self.params[1]]
));

NewRulesFor(VMV_X, rec(
  Accum_X_Dot := rec(
    info := "Selective accumulation of dot products <Mi,v2> over existing elements in v1",
    maxSize := false,
    applicable := (self, t) >> true,
    apply := (nt, C, ct) -> let(i := Ind(), sr := nt.sr, x_n := nt.x_n,
              Accum_X(sr, x_n, i, nt.start1, nt.d1, Dot_X(sr, x_n, [i, nt.startm[2]], nt.start2, nt.d2)))
  )
));

NewRulesFor(TriangleCount, rec(
  Accum_VMV_FLAME1 := rec(
    info := "FLAME Approach #1",
    maxSize := false,
    applicable := nt -> not nt.hasTags(),
    children := (self, nt) >> let(i := Ind(), n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              [[ VMV_X(sr, [i, V(0)], [V(0), V(0)], [i, V(0)], i, i, n) ]]),
    apply := (nt, C, ct) -> let(i := C[1]._children[1].rt.node.d1, n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              Assign_Ptr(Scale_Pt(V(0.5), Accum(sr, i, V(1), n, C[1]))))
  ),
  Accum_VMV_FLAME2 := rec(
    info := "FLAME Approach #2",
    maxSize := false,
    applicable := nt -> not nt.hasTags(),
    children := (self, nt) >> let(i := Ind(), n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              [[ VMV_X(sr, [i, V(0)], [V(0), i+1], [i, i+1], i, n-i-1, n) ]]),
    apply := (nt, C, ct) -> let(i := C[1]._children[1].rt.node.d1, n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              Assign_Ptr(Accum(sr, i, V(1), n-1, C[1])))
  ),
  Accum_VMV_FLAME4 := rec(
    info := "FLAME Approach #4",
    maxSize := false,
    applicable := nt -> not nt.hasTags(),
    children := (self, nt) >> let(i := Ind(), n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              [[ VMV_X(sr, [i-1, i], [i, i], [i-1, i], n-i, n-i, n) ]]),
    apply := (nt, C, ct) -> let(i := C[1]._children[1].rt.node.startm[1], n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              Assign_Ptr(Scale_Pt(V(0.5), Accum(sr, i, V(1), n, C[1]))))
  ),
  Accum_VMV_FLAME2_SMP := rec(
    info := "FLAME Approach #2",
    maxSize := false,
    applicable := nt -> nt.hasTags(),
    children := (self, nt) >> let(i := Ind(), n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              [[ VMV_X(sr, [i, V(0)], [V(0), i+1], [i, i+1], i, n-i-1, n) ]]),
    apply := (nt, C, ct) -> let(i := C[1]._children[1].rt.node.d1, n := nt.params[1], sr := TSemiring_Arithmetic(n.t),
              Assign_Ptr(SMP_Accum(sr, i, V(1), n-1, C[1], nt.firstTag().params[1])))
  )
));

NewRulesFor(KTruss, rec(
  TC_Support_Prune := rec(
    info := "Triangle Count for Support, then Prune. Iterate",
    maxSize := false,
    applicable := nt -> not nt.hasTags(),
    children := (self, nt) >> let(n := nt.params[1], m := nt.params[2], k := nt.params[3], s := var.fresh_t("s", TArray(TInt, nt.params[2])),
              [[ TriangleCountEdges(n, m, s), KTrussPrune(n, m, k, s) ]]),
    apply := (nt, C, ct) -> Assign_Ptr(Scale_Pt(V(0.5), IterateIndefinite(C[1], C[2])))
  ),
  TC_Support_Prune_SMP := rec(
    info := "Triangle Count for Support, then Prune. Iterate",
    maxSize := false,
    applicable := nt -> nt.hasTags(),
    children := (self, nt) >> let(n := nt.params[1], m := nt.params[2], k := nt.params[3], s := var.fresh_t("s", TArray(TInt, nt.params[2])),
              [[ TriangleCountEdges(n, m, s).withTags(nt.tags), KTrussPrune(n, m, k, s) ]]),
    apply := (nt, C, ct) -> Assign_Ptr(Scale_Pt(V(0.5), IterateIndefinite(C[1], C[2])))
  )
));

NewRulesFor(TriangleCountEdges, rec(
  Endpoint_Row_Dot := rec(
    info := "Dot products of rows corresponding to endpoints of each edge",
    maxSize := false,
    applicable := nt -> not nt.hasTags(),
    apply := (nt, C, ct) -> let(v1 := var.fresh_t("v", TInt), v2 := var.fresh_t("v", TInt), n := nt.params[1], m := nt.params[2], s := nt.params[3], sr := TSemiring_Arithmetic(n.t),
              Scat_E(sr, n, v1, v2, m, s, Dot_X(sr, n, [v1, 1], [v2, 1], n)))
  ),
  Endpoint_Row_Dot_SMP := rec(
    info := "Dot products of rows corresponding to endpoints of each edge",
    maxSize := false,
    applicable := nt -> nt.hasTags(),
    apply := (nt, C, ct) -> let(v1 := var.fresh_t("v", TInt), v2 := var.fresh_t("v", TInt), n := nt.params[1], m := nt.params[2], s := nt.params[3], sr := TSemiring_Arithmetic(n.t),
              SMP_Scat_E(sr, n, v1, v2, m, s, Dot_X(sr, n, [v1, 1], [v2, 1], n), nt.firstTag().params[1]))
  )
));

NewRulesFor(KTrussPrune, rec(
  Check_Support_Prune := rec(
    info := "Eliminate edges without enough support and break if nothing changes",
    maxSize := false,
    applicable := nt -> not nt.hasTags(),
    apply := (nt, C, ct) -> let(i := Ind(), v := var.fresh_t("v", TInt), n := nt.params[1], m := nt.params[2], k := nt.params[3], s := nt.params[4],
              CheckEdgesUpdate(m, v, PruneEdges(i, m, v, n, s, k)))
  )
));

DefaultSumsGen.Accum := (self, o, opts) >> Accum(o.sr, o.var, o.range_min, o.range_max, self(o.func(), opts));

DefaultSumsGen.Accum_X := (self, o, opts) >> Accum_X(o.sr, o.x_n, o.var, o.start, o.range, self(o.func(), opts));

DefaultSumsGen.Scat_E := (self, o, opts) >> Scat_E(o.sr, o.x_n, o.v1, o.v2, o.range, o.svar, self(o.func(), opts));

DefaultSumsGen.SMP_Scat_E := (self, o, opts) >> SMP_Scat_E(o.sr, o.x_n, o.v1, o.v2, o.range, o.svar, self(o.func(), opts), o.nthreads);

DefaultSumsGen.Dot_X := (self, o, opts) >> o;

DefaultSumsGen.CheckEdgesUpdate := (self, o, opts) >> CheckEdgesUpdate(o.edges, o.var, self(o.func(), opts));

DefaultSumsGen.PruneEdges := (self, o, opts) >> o;

DefaultSumsGen.SMP_Accum := (self, o, opts) >> SMP_Accum(o.sr, o.var, o.range_min, o.range_max, self(o.func(), opts), o.nthreads);

DefaultSumsGen.Assign_Ptr := (self, o, opts) >> Assign_Ptr(self(o.func(), opts));

DefaultSumsGen.Scale_Pt := (self, o, opts) >> Scale_Pt(o.scalar, self(o.func(), opts));

DefaultSumsGen.IterateIndefinite := (self, o, opts) >> IterateIndefinite(self(o.op1(), opts), self(o.op2(), opts));

DefaultCodegen.Dot_X := meth(self, o, y, x, opts)
  local j1, j1m, j2, j2m;
  if (IsBound(opts.isCSR) and opts.isCSR) then
    j1 := var.fresh_t("j1", x.t);
    j1m := var.fresh_t("j1m", x.t);
    j2 := var.fresh_t("j2", x.t);
    j2m := var.fresh_t("j2m", x.t);
    return decl([j1, j1m, j2, j2m], chain(
      assign(y, o.sr.zero()),
      assign(j1, add(x, o.x_n, V(1), nth(x, o.start1[1]))),
      assign(j1m, add(x, o.x_n, V(1), nth(x, add(o.start1[1], V(1))))),
      assign(j2, add(x, o.x_n, V(1), nth(x, o.start2[1]))),
      assign(j2m, add(x, o.x_n, V(1), nth(x, add(o.start2[1], V(1))))),
      loopw(logic_and(lt(j1, j1m), lt(deref(j1), o.start1[2])),
        assign(j1, add(j1, V(1)))),
      loopw(logic_and(lt(j2, j2m), lt(deref(j2), o.start2[2])),
        assign(j2, add(j2, V(1)))),
      loopw(logic_and(logic_and(lt(j1, j1m), lt(j2, j2m)), logic_and(lt(deref(j1), add(o.start1[2], o.range)), lt(deref(j2), add(o.start2[2], o.range)))),
        if3(lt(deref(j1), deref(j2)),
          assign(j1, add(j1, V(1))),
        lt(deref(j2), deref(j1)),
          assign(j2, add(j2, V(1))),
        chain(
          assign(y, o.sr.sum(y, V(1))),
          assign(j1, add(j1, V(1))),
          assign(j2, add(j2, V(1))))))));
  fi;
end;

DefaultCodegen.Accum_X := meth(self, o, y, x, opts)
  local j, jm, t;
  t := var.fresh_t("t", y.t);
  if (IsBound(opts.isCSR) and opts.isCSR) then
    j := var.fresh_t("j", x.t);
    jm := var.fresh_t("jm", x.t);
    return decl([j, jm], chain(
      assign(y, o.sr.zero()),
      assign(j, add(x, o.x_n, V(1), nth(x, o.start[1]))),
      assign(jm, add(x, o.x_n, V(1), nth(x, add(o.start[1], V(1))))),
      loopw(logic_and(lt(j, jm), lt(deref(j), o.start[2])),
        assign(j, add(j, V(1)))),
      loopw(logic_and(lt(j, jm), lt(deref(j), add(o.start[2], o.range))), decl([o.var, t], chain(
        assign(o.var, deref(j)),
        self(o.func(), t, x, opts),
        assign(y, o.sr.sum(y, t)),
        assign(j, add(j, V(1))))))));
  fi;
end;

DefaultCodegen.Scat_E := meth(self, o, y, x, opts)
  local t, i;
  t := var.fresh_t("t", y.t);
  i := Ind();
  if (IsBound(opts.isCSR) and opts.isCSR) then
    return decl([o.v1, o.svar], chain(
      assign(o.v1, V(0)),
      loopf(i, V(0), o.range, decl([t, o.v2], chain(
        loopw(eq(nth(x, add(o.v1, V(1))), i),
          assign(o.v1, add(o.v1, V(1)))),
        assign(o.v2, nth(x, add(o.x_n, V(1), i))),
        self(o.func(), t, x, opts),
        assign(nth(o.svar, i), t))))));
  fi;
end;

DefaultCodegen.SMP_Scat_E := meth(self, o, y, x, opts)
  local t, i, j;
  t := var.fresh_t("t", y.t);
  i := Ind();
  j := Ind();
  if (IsBound(opts.isCSR) and opts.isCSR) then
    return decl([o.v1, o.svar],
      smp_loopf(i, V(0), o.x_n, o.nthreads,
        loopf(j, nth(x, i), nth(x, add(i, V(1))), decl([o.v1, o.v2, t], chain(
          assign(o.v1, i),
          assign(o.v2, nth(x, add(o.x_n, V(1), j))),
          self(o.func(), t, x, opts),
          assign(nth(o.svar, j), t))))));
  fi;
end;

DefaultCodegen.CheckEdgesUpdate := meth(self, o, y, x, opts)
  return decl([o.var], chain(
    assign(o.var, V(0)),
    assign(y, o.edges),
    self(o.func(), y, x, opts),
    if1(eq(o.edges, o.var),
      break_ic()),
    assign(o.edges, o.var),
    assign(y, o.var)));
end;

DefaultCodegen.PruneEdges := meth(self, o, y, x, opts)
  local t;
  t := var.fresh_t("t", y.t);
  if (IsBound(opts.isCSR) and opts.isCSR) then
    return decl([t], chain(
      assign(t, V(0)),
      loopf(o.idx, V(0), o.range, chain(
        loopw(eq(nth(x, add(t, V(1))), o.idx), chain(
          assign(t, add(t, V(1))),
          assign(nth(x, t), o.var))),
        if1(geq(nth(o.s, o.idx), sub(o.k, V(2))), chain(
          assign(nth(x, add(o.x_n, V(1), o.var)), nth(x, add(o.x_n, V(1), o.idx))),
          assign(o.var, add(o.var, V(1))))))),
      assign(nth(x, add(t, V(1))), o.var)));
  fi;
end;

DefaultCodegen.Accum := meth(self, o, y, x, opts)
  local t;
  t := var.fresh_t("t", y.t);
  return chain(
    assign(y, o.sr.zero()),
    loopf(o.var, o.range_min, o.range_max, decl([t], chain(
      self(o.func(), t, x, opts),
      assign(y, o.sr.sum(y, t))))));
end;

DefaultCodegen.SMP_Accum := meth(self, o, y, x, opts)
  local t;
  t := var.fresh_t("t", y.t);
  return chain(
    assign(y, o.sr.zero()),
    smp_reduct_loopf(o.sr, o.var, o.range_min, o.range_max, y, o.nthreads, decl([t], chain(
      self(o.func(), t, x, opts),
      assign(y, o.sr.sum(y, t))))));
end;

DefaultCodegen.Scale_Pt := meth(self, o, y, x, opts)
  return chain(
    self(o.func(), y, x, opts),
    assign(y, mul(o.scalar, y)));
end;

DefaultCodegen.Assign_Ptr := meth(self, o, y, x, opts)
  local t;
  t := var.fresh_t("t", y.t.t);
  return decl([t], chain(
    self(o._children[1], t, x, opts),
    assign(deref(y), t)));
end;

DefaultCodegen.IterateIndefinite := meth(self, o, y, x, opts)
  return loopw(V(1), chain(
    self(o.op1(), y, x, opts),
    self(o.op2(), y, x, opts)));
end;

GraphIndicesCS := [
    c -> Compile.pullDataDeclsRefs(c),
    c -> Compile.fastScalarize(c),
    c -> UnrollCode(c),
    c -> FlattenCode(c),
    c -> UntangleChain(c),
    #(c, opts) -> CopyPropagate.initial(c, opts),
    #(c, opts) -> HashConsts(c, opts),
    #c -> MarkDefUse(c),
    #(c, opts) -> BinSplit(c, opts),
    #c -> MarkDefUse(c),
    #CopyPropagate, # does CSE
    c -> Compile.declareVars(c)
];

TCDefaults := CopyFields(SpiralDefaults, rec(
  compileStrategy := GraphIndicesCS,
  X := var("IJ", TPtr(TInt)),
  XType := TPtr(TInt),
  arrayDataModifier := "",
  arrayBufModifier := "",
  Y := var("res", TPtr(TInt)),
  YType := TPtr(TInt),
  isCSR := true,
));


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

Class(AVX_LDST, rec(
    list := [],
    __call__ := meth(arg)
        local self;
	self := arg[1];
	Append(self.list, Drop(arg, 1));
    end,

    getList := self >> self.list :: SSE_LDST.list,

));

_evshuf1 := self >> let(
    a := _unwrap(self.args[1].ev()),
    p := self.args[2].p,
    self.semantic(a, p));

_evshuf2 := self >> let(
    a := _unwrap(self.args[1].ev()),
    b := _unwrap(self.args[2].ev()),
    p := self.args[3].p,
    self.semantic(a, b, p));

_evpack := self >> let(
    a := _unwrap(self.args[1].ev()),
    b := _unwrap(self.args[2].ev()),
    self.semantic(a, b, []));

#######################################################################################
#   AVX 4-way 64-bit floating-point instructions

vzero_4x64f_const := TVect(T_Real(64), 4).zero();
vzero_4x64f := () -> vzero_4x64f_const;

# Load
#
AVX_LDST(
    # __m256d _mm256_maskload_pd(double *a, __m256i mask);
    Class(vloadmask_4x64f, VecExp_4.unary(), rec(
        mask := n -> Reversed(Flat(List([1..4], i->["0x00000000", When(i <= n, "0x80000000", "0x00000000")])))
    )),

    # __m256d _mm256_broadcast_sd(const double *a)
    Class(vbroadcast_4x64f, VecExp_4.unary()),
    # __m256d _mm256_loadu_pd(double const *a)
    Class(vloadu_4x64f, VecExp_4.unary())
);

# Store
#
AVX_LDST(
    # void _mm256_maskstore_pd(double *a, __m256i mask, __m256d b);
    Class(vstoremask_4x64f, VecStoreCommand.ternary(), rec(
        mask := n -> Reversed(Flat(List([1..4], i->["0x00000000", When(i <= n, "0x80000000", "0x00000000")])))
    )),

    # void _mm256_storeu_pd(double *a, __m256d b)
    Class(vstoreu_4x64f, VecStoreCommand.binary())
);

Class(addsub_4x64f,   VecExp_4.binary(), rec(
    ev := self >> add(self.args[1], mul(self.t.value([-1.0, 1.0, -1.0, 1.0]), self.args[2])).ev(),
));

Class(fmaddsub_4x64f, VecExp_4.ternary());

# __m256d _mm256_insertf128_pd(__m256d a, __m128d b, int offset);
Class(vinsert_2l_4x64f, VecExp_4.binary(), rec(
    ev := self >> let(
        a := _unwrap(self.args[1].ev()),
        b := _unwrap(self.args[2].ev()),
        When( self.args[3].p[1] = 0,
            b :: a{[3 .. 4]},
            a{[1 .. 2]} :: b )),
    computeType := self >> self.args[1].t,
));

# __m128d _mm256_extractf128_pd(__m256d a, int offset);
Class(vextract_2l_4x64f, VecExp_4.unary(), rec(
    semantic := (in1, p) -> in1{[1+2*p[1] .. 2 + 2*p[1]]},
    ev       := _evshuf1,

    computeType := self >> Checked(IsVecT(self.args[1].t),
	TVect(self.args[1].t.t, 2)), #self.args[1].t.size/2,
));

# Binary shuffle
#

# __m256d _mm256_unpacklo_pd(__m256d a, __m256d b);
Class(vunpacklo_4x64f, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> [in1[1], in2[1], in1[3], in2[3]],
    ev := _evpack
));

# __m256d _mm256_unpackhi_pd(__m256d a, __m256d b);
Class(vunpackhi_4x64f, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> [in1[2], in2[2], in1[4], in2[4]],
    ev := _evpack
));

# __m256d _mm256_shuffle_pd(__m256d a, __m256d b, const int select);
Class(vshuffle_4x64f,  VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> [in1[p[1]], in2[p[2]], in1[p[3]+2], in2[p[4]+2]],
    params := self >> [[1,2], [1,2], [1,2], [1,2]],
    ev := _evshuf2
));

# __m256d _mm256_permute2_pd(__m256d a, __m256d b, __m256i control, int imm);
Class(vperm2_4x64f,    VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> let(lane1 := Concat(in1{[1..2]}, in2{[1..2]}), lane2 := Concat(in1{[3..4]}, in2{[3..4]}),
                                    [lane1[p[1]], lane1[p[2]], lane2[p[3]], lane2[p[4]]]),
    params := self >> [[1..4], [1..4], [1..4], [1..4]],
    ev := _evshuf2
));

#__m256d _mm256_blend_pd(__m256d m1, __m256d m2, const int mask);
Class(vblend_4x64f,  VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> List( Zip2(TransposedMat([in1, in2]), p), e -> e[1][e[2]]),
    params := self >> Replicate(4, [1,2]),
    ev := _evshuf2
));

# __m256d _mm256_permute2f128_pd(__m256d a, __m256d b, int control);
Class(vpermf128_4x64f, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> let(v128 := [[in1[1], in1[2]], [in1[3], in1[4]], [in2[1], in2[2]], [in2[3], in2[4]]],
                                    Concat(v128[p[1]], v128[p[2]])),
    params := self >> [[1..4], [1..4]],
    ev := _evshuf2
));

# Unary shuffle
#

# __m256d _mm256_permute_pd(__m256d a, int control);
Class(vperm_4x64f, VecExp_4.unary(), rec(
    semantic := (in1, p) -> [in1[p[1]], in1[p[2]], in1[p[3]+2], in1[p[4]+2]],
    params := self >> [[1,2], [1,2], [1,2], [1,2]],
    ev := _evshuf1
));

Class(vuunpacklo_4x64f, VecExp_4.unaryFromBinop(vunpacklo_4x64f));
Class(vuunpackhi_4x64f, VecExp_4.unaryFromBinop(vunpackhi_4x64f));
Class(vushuffle_4x64f,  VecExp_4.unaryFromBinop(vshuffle_4x64f));
Class(vuperm2_4x64f,    VecExp_4.unaryFromBinop(vperm2_4x64f));
Class(vupermf128_4x64f, VecExp_4.unaryFromBinop(vpermf128_4x64f));

#######################################################################################
#   AVX 8-way 32-bit float instructions

vzero_8x32f_const := TVect(T_Real(64), 8).zero();
vzero_8x32f := () -> vzero_8x32f_const;

Class(addsub_8x32f,   VecExp_8.binary(), rec(
    ev := self >> add(self.args[1], mul(self.t.value([-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0]), self.args[2])).ev(),
));
Class(fmaddsub_8x32f, VecExp_8.ternary());

# Load
#
AVX_LDST(
    # __m256 _mm256_maskload_ps(float *a, __m256i mask);
    Class(vloadmask_8x32f, VecExp_8.unary(), rec(
        mask := n -> Reversed(List([1..8], i -> When(i <= n, "0x80000000", "0x00000000")))
    )),

    # __m256 _mm256_loadu_ps(float const *a)
    Class(vloadu_8x32f, VecExp_8.unary())
);

# Store
#
AVX_LDST(
    # void _mm256_maskstore_ps(float *a, __m256i mask, __m256 b);
    Class(vstoremask_8x32f, VecStoreCommand.ternary(), rec(
        mask := n -> Reversed(List([1..8], i->When(i <= n, "0x80000000", "0x00000000")))
    )),

    # void _mm256_storeu_ps(float *a, __m256 b)
    Class(vstoreu_8x32f, VecStoreCommand.binary())
);

# __m256 _mm256_insertf128_ps(__m256 a, __m128d , int offset);
Class(vinsert_4l_8x32f, VecExp_4.binary(), rec(
    ev := self >> let(
        a := _unwrap(self.args[1].ev()),
        b := _unwrap(self.args[2].ev()),
        When( self.args[3].p[1] = 0,
            b :: a{[5 .. 8]},
            a{[1 .. 4]} :: b )),
    computeType := self >> self.args[1].t,
));

# __m128 _mm256_extractf128_pd(__m256 a, int offset);
Class(vextract_4l_8x32f, VecExp_8.unary(), rec(
    semantic := (in1, p) -> in1{[1+4*p[1] .. 4 + 4*p[1]]},
    ev       := _evshuf1,

    computeType := self >> Checked(IsVecT(self.args[1].t),
	TVect(self.args[1].t.t, 4)), #self.args[1].t.size/2,
));

# Binary shuffle
#

# __m256 _mm256_unpacklo_ps(__m256 a, __m256 b);
Class(vunpacklo_8x32f, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> [in1[1], in2[1], in1[2], in2[2], in1[5], in2[5], in1[6], in2[6]],
    ev := _evpack,
));

# __m256 _mm256_unpackhi_ps(__m256 a, __m256 b);
Class(vunpackhi_8x32f, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> [in1[3], in2[3], in1[4], in2[4], in1[7], in2[7], in1[8], in2[8]],
    ev := _evpack,
));

# __m256 _mm256_permute2f128_ps(__m256 a, __m256 b, int control);
Class(vpermf128_8x32f, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> let(v128 := [in1{[1..4]}, in1{[5..8]}, in2{[1..4]}, in2{[5..8]}],
                                    Concat(v128[p[1]], v128[p[2]])),
    params := self >> [[1..4], [1..4]],
    ev := _evshuf2,
));

# __m256 _mm256_permute2_ps(__m256 a, __m256 b, __m256i control, int imm);
Class(vperm2_8x32f, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> let(lane1 := Concat(in1{[1..4]}, in2{[1..4]}), lane2 := Concat(in1{[5..8]}, in2{[5..8]}),
                                    [lane1[p[1]], lane1[p[2]], lane1[p[3]], lane1[p[4]], lane2[p[1]], lane2[p[2]], lane2[p[3]], lane2[p[4]]]),
    params := self >> [[1..8], [1..8], [1..8], [1..8]],
    ev := _evshuf2
));

# __m256 _mm256_shuffle_ps(__m256 a, __m256 b, const int select);
Class(vshuffle_8x32f, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> [in1[p[1]], in1[p[2]], in2[p[3]], in2[p[4]], in1[p[1]+4], in1[p[2]+4], in2[p[3]+4], in2[p[4]+4]],
    params := self >> [[1..4], [1..4], [1..4], [1..4]],
    ev := _evshuf2,
));

#__m256 _mm256_blend_ps(__m256 m1, __m256 m2, const int mask);
Class(vblend_8x32f,  VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> List( Zip2(TransposedMat([in1, in2]), p), e -> e[1][e[2]]),
    params := self >> Replicate(8, [1,2]),
    ev := _evshuf2
));

# Unary shuffle
#

# __m256 _mm256_permute_ps(__m256 a, int control);
Class(vperm_8x32f, VecExp_8.unary(), rec(
    semantic := (in1, p) -> [in1[p[1]], in1[p[2]], in1[p[3]], in1[p[4]], in1[p[1]+4], in1[p[2]+4], in1[p[3]+4], in1[p[4]+4]],
    params := self >> [[1..4], [1..4], [1..4], [1..4]],
    ev := _evshuf1
));

Class(vuunpacklo_8x32f, VecExp_8.unaryFromBinop(vunpacklo_8x32f));
Class(vuunpackhi_8x32f, VecExp_8.unaryFromBinop(vunpackhi_8x32f));
Class(vushuffle_8x32f,  VecExp_8.unaryFromBinop(vshuffle_8x32f));
Class(vupermf128_8x32f, VecExp_8.unaryFromBinop(vpermf128_8x32f));

# __m256 _mm256_permutevar_ps(__m256 a, __m256i control);
Class(vpermv_8x32f, VecExp_8.unary(), rec(
    semantic := (in1, p) -> [in1[p[1]], in1[p[2]], in1[p[3]], in1[p[4]], in1[p[5]+4], in1[p[6]+4], in1[p[7]+4], in1[p[8]+4]],
    params := self >> Replicate(8, [1,2]),
    ev := _evshuf1,
));

# __m256 _mm256_movehdup_ps (__m256 a);
Class(vhdup_8x32f, VecExp_8.unary(), rec(
    semantic := (in1, p) -> [in1[2], in1[2], in1[4], in1[4], in1[6], in1[6], in1[8], in1[8]],
    ev := self >> let( v := self._vval(1), self.t.value(self.semantic(v, []))),
));

# __m256 _mm256_moveldup_ps (__m256 a);
Class(vldup_8x32f, VecExp_8.unary(), rec(
    semantic := (in1, p) -> [in1[1], in1[1], in1[3], in1[3], in1[5], in1[5], in1[7], in1[7]],
    ev := self >> let( v := self._vval(1), self.t.value(self.semantic(v, []))),
));

# vcxtr_8x32f is a virtual instruction: in-register Tensor(L(4,2),I(2))
#    later rewritten to vblend_8x32f(x, vperm_8x32f(vupermf128_8x32f(x, [ 2, 1 ]), [3,4,1,2]), [1,1,2,2,2,2,1,1])
#    this helps SIMD_ISA_DB to find this permutation
Class(vcxtr_8x32f, VecExp_8.unary(), rec(
    semantic := (in1, p) -> [in1[1], in1[2], in1[5], in1[6], in1[3], in1[4], in1[7], in1[8]],
    ev := self >> let( v := self._vval(1), self.t.value(self.semantic(v, []))),
    vcost := 5, # 3 instructions + dependency penalty
));

# Type Conversion

# __m256 _mm256_cvtpd_ps (__m256d a)
Class(vcvt_8x32f_4x64f, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Real(32), 8)
));

# __m256d _mm256_cvtps_pd (__m128 a)
Class(vcvt_4x64f_4x32f, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Real(64), 4)
));

# __m256d _mm256_cvtepi32_pd (__m128i src)
Class(vcvt_4x64f_4x32i, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Real(64), 4)
));

# __m128i _mm256_cvtpd_epi32 (__m256d src), rounding
Class(vcvt_4x32i_4x64f, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Int(32), 4)
));

# __m128i _mm256_cvttpd_epi32 (__m256d src), truncation
Class(vcvtt_4x32i_4x64f, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Int(32), 4)
));

# __m256 _mm256_cvtepi32_ps (__m256i src)
Class(vcvt_8x32f_8x32i, VecExp_8.unary(), rec(
    computeType := (self) >> TVect(T_Real(32), 8)
));

# type conversion 8x32f to 8x32i, rounding
# __m256i _mm256_cvtps_epi32 (__m256 a)
Class(vcvt_8x32i_8x32f, VecExp_8.unary(), rec(
    computeType := (self) >> TVect(T_Int(32), 8)
));

# type conversion 8x32f to 8x32i, truncation toward zero
# __m256i _mm256_cvttps_epi32 (__m256 a)
Class(vcvtt_8x32i_8x32f, VecExp_8.unary(), rec(
    computeType := (self) >> TVect(T_Int(32), 8)
));


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

reps:=[[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1]],            # E_7
       [[1,1],[3,1],[10,1],[9,1],[5,1],[6,1],[7,1]],           # E_7(a_1)
       [[1,1],[2,1],[3,1],[9,1],[11,1],[12,1],[13,1]],         # E_7(a_2)
       [[15,1],[1,1],[17,1],[6,1],[7,1],[16,1],[4,1]],         # E_7(a_3) asta functioneaza in caracteristica 3
#       [[1,1],[4,1],[7,1],[11,1],[12,1],[15,1],[17,1]],        # E_7(a_3)
       [[1,1],[2,1],[3,1],[4,1],[5,1],[6,1]],                  # E_6
#       [[3,1],[5,1],[1,1],[6,1],[10,1],[11,1],[2,1]],          # E_6(a_1)
       [[1,1],[2,1],[10,1],[11,1],[5,1],[6,1]],                # E_6(a_1)
       [[7,1],[6,1],[5,1],[4,1],[2,1],[3,1]],                  # D_6
       [[1,1],[4,1],[7,1],[13,1],[15,1],[16,1],[17,1],[23,1]], # E_7(a_4) acelas ca cel mai de jos dar ordonat
#       [[15,1],[1,1],[17,1],[13,1],[23,1],[7,1],[16,1],[4,1]], # E_7(a_4) asta functioneaza in caracteristica 3
#       [[1,1],[4,1],[7,1],[10,1],[22,1],[23,1],[24,1]],        # E_7(a_4)
#       [[7,1],[6,1],[5,1],[9,1],[10,-1],[2,1],[3,1]],          # D_6(a_1)
       [[2,1],[3,1],[9,1],[11,1],[6,1],[7,1]],                # D_6(a_1)
       [[1,1],[3,1],[4,1],[2,1],[5,1],[7,1]],                  # D_5A_1
       [[1,1],[3,1],[4,1],[5,1],[6,1],[7,1]],                  # A_6
#       [[21,1],[23,1],[7,1],[24,1],[20,1],[4,1],[22,1]],       # E_7(a_5)
       [[4,1],[10,1],[13,1],[14,1],[21,1],[22,1],[23,1]],      # E_7(a_5)
       [[1,1],[3,1],[4,1],[2,1],[5,1]],                        # D_5
       [[15,1],[1,1],[17,1],[6,1],[16,1],[4,1]],               # E_6(a_3) this works in characteristic 3
#       [[4,1],[6,1],[8,1],[10,1],[16,1],[17,1]],               # E_6(a_3)
#       [[7,1],[12,1],[9,1],[10,-1],[11,1],[2,1],[3,1]],        # D_6(a_2)
       [[2,1],[3,1],[9,1],[11,1],[12,1],[13,1]],              # D_6(a_2)
#       [[1,1],[3,1],[9,1],[11,1],[2,1],[5,1],[7,1]],           # D_5(a_1)A_1
       [[1,1],[9,1],[10,1],[2,1],[5,1],[7,1]],                # D_5(a_1)A_1

       [[2,1],[4,1],[5,1],[6,1],[7,1],[1,1]],       # A_5A_1
       [[1,1],[3,1],[4,1],[5,1],[6,1]],             # (A_5)'
       [[1,1],[3,1],[4,1],[2,1],[6,1],[7,1]],       # A_4A_2
#       [[1,1],[3,1],[9,1],[11,1],[2,1],[5,1]],      # D_5(a_1)
#       [[1,1],[10,1],[11,1],[2,1],[5,1]],           # D_5(a_1) ALSO WORKS?
       [[1,1],[9,1],[10,1],[2,1],[5,1]],           # D_5(a_1)
       [[1,1],[2,1],[3,1],[4,1],[6,1]],             # A_4A_1
       [[3,1],[4,1],[2,1],[5,1],[7,1]],             # D_4A_1
       [[2,1],[4,1],[5,1],[6,1],[7,1]],             # (A_5)''
       [[1,1],[3,1],[2,1],[5,1],[6,1],[7,1]],       # A_3A_2A_1
       [[1,1],[2,1],[3,1],[4,1]],                   # A_4
#       [[3,1],[4,1],[2,1],[6,1],[7,1]],             # A_3A_2
       [[3,1],[4,1],[2,1],[6,1],[7,1]],             # A_3A_2
#       [[3,1],[9,1],[11,1],[2,1],[5,1],[7,1]],      # D_4(a_1)A_1
#       [[2,1],[3,1],[9,1],[11,1],[7,1]],           # D_4(a_1)A_1
       [[2,1],[5,1],[9,1],[10,1],[7,1]],           # D_4(a_1)A_1  cel de deasupra simetric fata de D4
       [[3,1],[4,1],[2,1],[5,1]],                   # D_4
       [[5,1],[6,1],[7,1],[2,1],[3,1]],             # A_3A_1^2
#       [[3,1],[9,1],[11,1],[2,1],[5,1]],            # D_4(a_1)
       [[2,1],[3,1],[9,1],[11,1]],                 # D_4(a_1)
       [[1,1],[3,1],[4,1],[6,1]],                   # (A_3A_1)'
       [[1,1],[3,1],[5,1],[6,1],[2,1]],             # A_2^2A_1
       [[5,1],[6,1],[7,1],[2,1]],                   # (A_3A_1)''
       [[1,1],[3,1],[2,1],[5,1],[7,1]],             # A_2A_1^3
       [[1,1],[3,1],[5,1],[6,1]],                   # A_2^2
       [[1,1],[3,1],[4,1]],                         # A_3
       [[2,1],[4,1],[1,1],[6,1]],                   # A_2A_1^2
       [[1,1],[3,1],[2,1]],                         # A_2A_1
       [[2,1],[3,1],[5,1],[7,1]],                   # A_1^4
       [[1,1],[3,1]],                               # A_2
       [[1,1],[4,1],[6,1]],                         # (A_1^3)'
       [[2,1],[5,1],[7,1]],                         # (A_1^3)''
       [[1,1],[6,1]],                               # A_1^2
       [[1,1]]                                      # A_1
       ];

I:=[[],          # E_7
    [4],         # E_7(a_1)
    [4,6],       # E_7(a_2)
#    [2,3,5],     # E_7(a_3)
    [],          # E_7(a_3)
    [],          # E_6
#    [4],         # E_6(a_1)
    [],          # E_6(a_1)
    [],          # D_6
#    [2,3,5,6],   # E_7(a_4)
    [],          # E_7(a_4)
#    [4],         # D_6(a_1)
    [],          # D_6(a_1)
    [],          # D_5A_1
    [],          # A_6
#    [1,2,3,5,6], # E_7(a_5)
    [],          # E_7(a_5)
    [],          # D_5
#    [2,3,5],     # E_6(a_3)
    [],          # E_6(a_3)
#    [4,5],       # D_6(a_2)    ???
    [],          # D_6(a_2)    ???
#    [4],         # D_5(a_1)A_1 ???
    [],          # D_5(a_1)A_1 ???

    [],          # A_5A_1
    [],          # (A_5)'
    [],          # A_4A_2
#    [4],         # D_5(a_1)    ???
    [],          # D_5(a_1)    ???
    [],          # A_4A_1
    [],          # D_4A_1
    [],          # (A_5)''
    [],          # A_3A_2A_1
    [],          # A_4
    [],          # A_3A_2
#    [4],         # D_4(a_1)A_1
    [],          # D_4(a_1)A_1
    [],          # D_4
    [],          # A_3A_1^2
#    [4],         # D_4(a_1)
    [],          # D_4(a_1)
    [],          # (A_3A_1)'
    [],          # A_2^2A_1
    [],          # (A_3A_1)''
    [],          # A_2A_1^3
    [],          # A_2^2
    [],          # A_3
    [],          # A_2A_1^2
    [],          # A_2A_1
    [],          # A_1^4
    [],          # A_2
    [],          # (A_1^3)'
    [],          # (A_1^3)''
    [],          # A_1^2
    []           # A_1
    ];

#
# The parabolic where the representative is distinguished
#
II:=[[1,2,3,4,5,6,7],  # E_7
     [1,2,3,4,5,6,7],  # E_7(a_1)
     [1,2,3,4,5,6,7],  # E_7(a_2)
     [1,2,3,4,5,6,7],  # E_7(a_3)
#     [],  # E_7(a_3)
     [1,2,3,4,5,6],    # E_6
     [1,2,3,4,5,6],    # E_6(a_1)
#     [],    # E_6(a_1)
     [2,3,4,5,6,7],    # D_6
     [1,2,3,4,5,6,7],  # E_7(a_4)
#     [],  # E_7(a_4)
     [2,3,4,5,6,7],    # D_6(a_1)
#     [],    # D_6(a_1)
     [1,2,3,4,5,7],    # D_5A_1
     [1,3,4,5,6,7],    # A_6
     [1,2,3,4,5,6,7],  # E_7(a_5)
#     [],  # E_7(a_5)
     [1,2,3,4,5],      # D_5
     [1,2,3,4,5,6],    # E_6(a_3)
#     [],    # E_6(a_3)
     [2,3,4,5,6,7],    # D_6(a_2)
#     [],    # D_6(a_2)
     [1,2,3,4,5,7],    # D_5(a_1)A_1
#     [],    # D_5(a_1)A_1

     [1,2,4,5,6,7],    # A_5A_1
     [1,3,4,5,6],      # (A_5)'
     [1,2,3,4,6,7],    # A_4A_2
     [1,2,3,4,5],      # D_5(a_1)
#     [],      # D_5(a_1)
     [1,2,3,4,6],      # A_4A_1
     [2,3,4,5,7],      # D_4A_1
     [2,4,5,6,7],      # (A_5)''
     [1,2,3,5,6,7],    # A_3A_2A_1
     [1,2,3,4],        # A_4
     [2,3,4,6,7],      # A_3A_2
     [2,3,4,5,7],      # D_4(a_1)A_1
#     [],      # D_4(a_1)A_1
     [2,3,4,5],        # D_4
     [2,3,5,6,7],      # A_3A_1^2
     [2,3,4,5],        # D_4(a_1)
#     [],        # D_4(a_1)
     [1,3,4,6],        # (A_3A_1)'
     [1,2,3,5,6],      # A_2^2A_1
     [2,5,6,7],        # (A_3A_1)''
     [1,2,3,5,7],      # A_2A_1^3
     [1,3,5,6],        # A_2^2
     [1,3,4],          # A_3
     [1,2,4,6],        # A_2A_1^2
     [1,2,3],          # A_2A_1
     [2,3,5,7],        # A_1^4
     [1,3],            # A_2
     [1,4,6],          # (A_1^3)'
     [2,5,7],          # (A_1^3)''
     [1,6],            # A_1^2
     [1]               # A_1
     ];

labels:=["E_7",
         "E_7(a_1)",
         "E_7(a_2)",
         "E_7(a_3)",
         "E_6",
         "E_6(a_1)",
         "D_6",
         "E_7(a_4)",
         "D_6(a_1)",
         "D_5A_1",
         "A_6",
         "E_7(a_5)",
         "D_5",
         "E_6(a_3)",
         "D_6(a_2)",
         "D_5(a_1)A_1",

         "A_5A_1",
         "(A_5)1",
         "A_4A_2",
         "D_5(a_1)",
         "A_4A_1",
         "D_4A_1",
         "(A_5)2",
         "A_3A_2A_1",
         "A_4",
         "A_3A_2",
         "D_4(a_1)A_1",
         "D_4",
         "A_3A_1^2",
         "D_4(a_1)",
         "(A_3A_1)1",
         "A_2^2A_1",
         "(A_3A_1)2",
         "A_2A_1^3",
         "A_2^2",
         "A_3",
         "A_2A_1^2",
         "A_2A_1",
         "A_1^4",
         "A_2",
         "(A_1^3)1",
         "(A_1^3)2",
         "A_1^2",
         "A_1"
         ];

tori:=[
       [2,2,2,2,2,2,2],   # E_7
       [2,2,2,0,2,2,2],   # E_7(a_1)
       [2,2,2,0,2,0,2],   # E_7(a_2)
       [2,0,0,2,0,2,2],   # E_7(a_3)
#    [],   # E_7(a_3)
       [2,2,2,2,2,2,-16], # E_6
       [2,2,2,0,2,2,-12], # E_6(a_1)
#    [], # E_6(a_1)
       [-15,2,2,2,2,2,2], # D_6
       [2,0,0,2,0,0,2],   # E_7(a_4)
#    [],   # E_7(a_4)
       [-11,2,2,0,2,2,2], # D_6(a_1)
#    [], # D_6(a_1)
       [2,2,2,2,2,-11,2], # D_5A_1
       [2,-12,2,2,2,2,2], # A_6
       [0,0,0,2,0,0,2],   # E_7(a_5)
#    [],   # E_7(a_5)
       [2,2,2,2,2,-10,0], # D_5
       [2,0,0,2,0,2,-8],  # E_6(a_3)
#    [],  # E_6(a_3)
       [-9,2,2,0,2,0,2],  # D_6(a_2)
#    [],  # D_6(a_2)
       [2,2,2,0,2,-8,2],  # D_5(a_1)A_1
#    [],  # D_5(a_1)A_1

       [2,2,-9,2,2,2,2],   # A_5A_1
       [2,-9,2,2,2,2,-5],  # (A_1)'
       [2,2,2,2,-8,2,2],   # A_4A_2
       [2,2,2,0,2,-7,0],   # D_5(a_1)
#    [],   # D_5(a_1)
       [2,2,2,2,-7,2,-1],  # A_4A_1
       [-6,2,2,2,2,-7,2],  # D_4A_1
       [0,2,-8,2,2,2,2],   # (A_5)''
       [2,2,2,-6,2,2,2],   # A_3A_2A_1
       [2,2,2,2,-6,0,0],   # A_4
       [-3,2,2,2,-6,2,2],  # A_3A_2
       [-4,2,2,0,2,-5,2],  # D_4(a_1)A_1
#    [],  # D_4(a_1)A_1
       [-6,2,2,2,2,-6,0],  # D_4
       [-1,2,2,-5,2,2,2],  # A_3A_1^2
       [-4,2,2,0,2,-4,0],  # D_4(a_1)
#    [],  # D_4(a_1)
       [2,-3,2,2,-4,2,-1], # (A_3A_1)'
       [2,2,2,-5,2,2,-2],  # A_2^2A_1
       [0,2,0,-4,2,2,2],   # (A_3A_1)''
       [2,2,2,-4,2,-2,2],  # A_2A_1^3
       [2,0,2,-4,2,2,-2],  # A_2^2
       [2,-3,2,2,-3,0,0],  # A_3
       [2,2,-3,2,-3,2,-1], # A_2A_1^2
       [2,2,2,-3,0,0,0],   # A_2A_1
       [-1,2,2,-3,2,-2,2], # A_1^4
       [2,0,2,-2,0,0,0],   # A_2
       [2,-1,-2,2,-2,2,-1],# (A_1^3)'
       [0,2,0,-2,2,-2,2],  # (A_1^3)''
       [2,0,-1,0,-1,2,-1], # A_1^2
       [2,0,-1,0,0,0,0],   # A_1
    ];

#blocks:=[];
blocks:=
[[[27, 3], [19, 1], [15, 1], [9, 2]], [[27, 1], [19, 1], [18, 2], [15, 1], [9, 4]], [[19, 1], [18, 2], [15, 1], [9, 7]], [[19, 1], [17, 1], [15, 2], [11, 2], [9, 3], [6, 2], [3, 2]], [[19, 1], [15, 4], [9, 5], [3, 3]], [[9, 14], [7, 1]], [[19, 1], [16, 2], [15, 1], [11, 1], [10, 2], [9, 2], [6, 2], [3, 1], [1, 3]], [[9, 12], [7, 1], [6, 2], [3, 2]], [[9, 11], [8, 2], [6, 2], [3, 1], [1, 3]], [[9, 11], [8, 2], [6, 2], [3, 1], [1, 3]], [[9, 11], [7, 4], [3, 1], [1, 3]], [[9, 9], [7, 1], [6, 2], [5, 3], [3, 6]], [[9, 11], [7, 4], [1, 6]], [[9, 6], [7, 1], [6, 8], [3, 8]], [[9, 9], [7, 1], [6, 2], [5, 1], [4, 4], [3, 3], [1, 3]], [[9, 5], [7, 4], [6, 6], [3, 7], [1, 3]], [[9, 9], [3, 16], [2, 2]], [[9, 5], [8, 2], [6, 8], [3, 7], [1, 3]], [[9, 3], [7, 1], [6, 8], [5, 3], [3, 12]], [[9, 3], [8, 4], [7, 2], [6, 6], [3, 4], [2, 4], [1, 4]], [[9, 1], [8, 2], [7, 3], [6, 4], [5, 5], [4, 2], [3, 8], [2, 2], [1, 2]], [[9, 2], [8, 4], [7, 5], [6, 4], [3, 2], [2, 4], [1, 10]], [[9, 9], [3, 15], [1, 7]], [[7, 1], [6, 8], [5, 3], [3, 21]], [[9, 1], [7, 7], [5, 9], [3, 7], [1, 9]], [[7, 1], [6, 8], [5, 1], [4, 4], [3, 18], [1, 3]], [[7, 1], [6, 6], [5, 4], [4, 4], [3, 16], [1, 6]], [[9, 2], [7, 13], [3, 1], [1, 21]], [[7, 1], [6, 4], [5, 7], [4, 4], [3, 11], [2, 6], [1, 6]], [[7, 1], [6, 2], [5, 12], [3, 15], [1, 9]], [[7, 1], [6, 2], [5, 7], [4, 10], [3, 6], [2, 6], [1, 9]], [[3, 43], [2, 2]], [[7, 1], [5, 15], [3, 10], [1, 21]], [[3, 42], [1, 7]], [[3, 42], [1, 7]], [[7, 1], [5, 7], [4, 16], [3, 1], [1, 24]], [[3, 37], [2, 8], [1, 6]], [[3, 34], [2, 8], [1, 15]], [[3, 28], [2, 14], [1, 21]], [[3, 33], [1, 34]], [[3, 19], [2, 26], [1, 24]], [[3, 27], [1, 52]], [[3, 10], [2, 32], [1, 39]], [[3, 1], [2, 32], [1, 66]]];

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

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


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

Declare(ScatHUnion, eUnion);

Class(ISumUnion, ISum, rec(
    toOperator := self >> (vec -> Checked(Length(vec)=self.dims()[2],
        Flat(List(TransposedMat(List([0..self.var.range-1],
        j->RulesStrengthReduce(SubstVars(Copy(self._children[1]), rec((self.var.id):=V(j)))).toOperator()(vec))), i->Filtered(i, k->k<>()))
    )))
));

Class(ISumReduction, ISum, rec(
    abbrevs := [ (v, rng, op, idval, isSaturated, expr) -> [v, rng, op, idval, isSaturated, expr] ],
    new := meth(self, var, domain, op, idval, isSaturated, expr)
        local res;
        Constraint(IsSPL(expr));
        # if domain is an integer (not symbolic) it must be positive
        Constraint(not IsInt(domain) or domain >= 0);
        var.isLoopIndex := true;
        res := SPL(WithBases(self, rec(_children := [expr], var := var, domain := domain, op := op, idval := idval, isSaturated := isSaturated)));
        res.dimensions := res.dims();
        return res;
    end,
    print := (self, i, is) >> Print(
        self.name, "(", self.var, ", ", self.domain, ", ", self.op, ", ", self.idval, ", ", self.isSaturated, ",\n",
        Blanks(i+is), self._children[1].print(i+is, is), "\n",
        Blanks(i), ")", self.printA(),
        When(IsBound(self._setDims), Print(".overrideDims(", self._setDims, ")"), Print(""))
    ),
    toOperator := self >> (vec -> Checked(Length(vec)=self.dims()[2],
        List(List(TransposedMat(List([0..self.var.range-1],
        j->RulesStrengthReduce(SubstVars(Copy(self._children[1]), rec((self.var.id):=V(j)))).toOperator()(vec))), i->Filtered(i, k->k<>())),
        e->_unwrap(FoldL(e, self.op, self.idval)))))
));

Class(GathH, SumsBase, BaseMat, rec(
    rChildren := self >> [self.N, self.n, self.base, self.stride],
    rSetChild := rSetChildFields("N", "n", "base", "stride"),
    new := (self, N, n, base, stride) >>
	SPL(WithBases(self, rec(
      	N:= N, n := n, base := base, stride := stride, dimensions := [n, N]))),
    dims := self >> self.dimensions,
    sums := self >> self,
    area := self >> self.n,
    isReal := self >> true,
    transpose := self >> ScatHUnion(self.N, self.n, self.base, self.stride),
    conjTranspose := self >> self.transpose(),
    inverse := self >> self.transpose(),
    toAMat := self >> let(
	   n := self.n,
       N := self.N,
       base := self.base,
       stride := self.stride,
       AMatMat(List([0..n-1], row -> let(
         idx := base+row*stride,
		 BasisVec(N, idx))))
    ),
    isIdentity := self >> (self.n=self.N) and self.base=0 and self.stride = 1,
    toOperator := self >> (vec-> Checked(Checked(Length(vec) = self.N, List([0..self.n-1], i->vec[_unwrap(self.base+i*self.stride+1)]))))
));

Class(ScatHUnion, SumsBase, BaseMat, rec(
    rChildren := self >> [self.N, self.n, self.base, self.stride],
    rSetChild := rSetChildFields("N", "n", "base", "stride"),
    new := (self, N, n, base, stride) >>

    SPL(WithBases(self, rec(
        N:= N, n := n, base := base, stride := stride, dimensions := [N, n]))),
    dims := self >> self.dimensions,
    sums := self >> self,
    area := self >> self.n,
    isReal := self >> true,
    transpose := self >> GathH(self.N, self.n, self.base, self.stride),
    conjTranspose := self >> self.transpose(),
    inverse := self >> self.transpose(),
    toAMat := self >> let(
	   n := self.n,
       N := self.N,
       base := self.base,
       stride := self.stride,
       TransposedAMat(AMatMat(List([0..n-1], row -> let(
         idx := base+row*stride,
		 BasisVec(N, idx))))
    )),
    isIdentity := self >> (self.n=self.N) and self.base=0 and self.stride = 1,
    toOperator := self >>(vec -> Checked(Length(vec) = self.n, List([0..self.N-1], j->let(i := (j-_unwrap(self.base))/_unwrap(self.stride), When(IsPosInt0(i) and i < _unwrap(self.n), vec[i+1], ())))))
));

Class(ScatHAcc, ScatHUnion);

Class(eT, SumsBase, BaseMat, rec(
    rChildren := self >> [self.N, self.base],
    rSetChild := rSetChildFields("N", "base"),
    new := (self, N, base) >> SPL(WithBases(self, rec(
      	N:= N, base := base, dimensions := [1, N]))),
    dims := self >> self.dimensions,
    sums := self >> self,
    area := self >> self.N,
    isReal := self >> true,
    transpose := self >> eUnion(self.N, self.base),
    conjTranspose := self >> self.transpose(),
    inverse := self >> self.transpose(),
    toAMat := self >> let(
	   n := self.n,
       N := self.N,
       base := self.base,
       stride := self.stride,
       AMatMat(List([0..n-1], row -> let(
         idx := base+row*stride,
		 BasisVec(N, idx))))
    ),
    isIdentity := self >> false,#(self.N=1),
    toOperator := self >>(vec -> Checked(Length(vec) = self.N, [vec[_unwrap(self.base)+1]]))
));

Class(eUnion, SumsBase, BaseMat, rec(
    rChildren := self >> [self.N, self.base],
    rSetChild := rSetChildFields("N", "base"),
    new := (self, N, base) >> SPL(WithBases(self, rec(
      	N:= N, base := base, dimensions := [N, 1]))),
    dims := self >> self.dimensions,
    sums := self >> self,
    area := self >> 1,
    isReal := self >> true,
    transpose := self >> eT(self.N, self.base),
    conjTranspose := self >> self.transpose(),
    inverse := self >> self.transpose(),
    toAMat := self >> let(
	   n := 1,
       N := self.N,
       base := self.base,
       stride := 1,
       TransposedAMat(AMatMat(List([0..n-1], row -> let(
         idx := base+row*stride,
		 BasisVec(N, idx))))
    )),
    isIdentity := False,
    toOperator := self >>(vec -> Checked(Length(vec) = 1, List([0..self.N-1], i->When(i = _unwrap(self.base), vec[1], ()))))
));

Class(Mul1, Blk1, rec(
    toOperator := self >> (vec -> Checked(Length(vec) = 1, [_unwrap(RulesStrengthReduce(vec[1]*_unwrap(self.element)))]))
));

Class(SUMUnion, SUM, rec(
    toOperator := self >> (vec -> Checked(Length(vec)=self.dims()[2],
        Flat(List(TransposedMat(List(self.children(), c->RulesStrengthReduce(c.toOperator()(vec)))), i->Filtered(i, k->k<>())))))
));

Class(Inductor,  BaseMat, rec(
    abbrevs := [ (N, i, op, initval) -> [N, i, op, initval] ],
    new := (self, N, i, op, initval) >> SPL(WithBases(self, rec(
        N := N, i := i, op := op, initval := initval, dimensions := [1, 1]))),
    print := (self, i, is) >> Print("Inductor(", self.N, ", ", self.i, ", ", self.op, ", ", self.initval, ")"),
    dims  := self >> self.dimensions,
    rChildren := self >> [self.N, self.i, self.op, self.initval],
    rSetChild := rSetChildFields("N", "i", "op", "initval"),
    isReal := True,
    recurrence := self >> ((j,v)->When(j=0, self.initval, self.op.at(self.recurrence()(j-1,v),v))),
    toOperator := self >> (vec -> Checked(Length(vec)=self.dims()[2], [List([0..self.N-1], i->_unwrap(RulesStrengthReduce(self.recurrence()(i,vec[1]))))[_unwrap(self.i)+1]]))
));


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

Class(APar, ASingletonTag);
Class(AVec, ASingletonTag);

IsParPar := P -> P[3] = APar and P[4] = APar;
IsVecVec := P -> P[3] = AVec and P[4] = AVec;
IsVecPar := P -> P[3] = AVec and P[4] = APar;
IsParVec := P -> P[3] = APar and P[4] = AVec;
IsParXXX := P -> P[3] = APar;
IsVecXXX := P -> P[3] = AVec;
IsXXXPar := P -> P[4] = APar;
IsXXXVec := P -> P[4] = AVec;

BaseOperation.doNotMeasure := true;
PermClass.doNotMeasure := true;

###################################################################
#F tSPL - base class for all tSPL constructs
#F *OBSOLETE*. Use Tagged_tSPL
#F

#D Class(tSPL, NonTerminal, rec(
#D    isTSPL := true
#D ));

###################################################################
#F Tagged_tSPL - base class for all tSPL constructs with tag support
#F
#F *!!* Should be used for all new constructs instead of tSPL
#F
Class(Tagged_tSPL, TaggedNonTerminal, rec(
    isTSPL := true
));

###################################################################
#F Tagged_tSPL_Container - base class for a generic tSPL container
#F
Class(Tagged_tSPL_Container, Tagged_tSPL, rec(
    abbrevs :=  [ nt -> Checked(IsSPL(nt), [nt]) ],
    dims := self >> self.params[1].dims(),
    advdims := self >> self.params[1].advdims(),
    terminate := self >> self.params[1].terminate(),
    transpose := self >> ObjId(self)(self.params[1].transpose())
                             .withTags(self.getTags()),
    isReal := self >> self.params[1].isReal(),
    isInplace := self >> self.params[1].isInplace(),
    normalizedArithCost := self >> self.params[1].normalizedArithCost(),
    doNotMeasure := true,
));

IsTSPL := o -> IsRec(o) and IsBound(o.isTSPL) and o.isTSPL;

Declare(TGrp, TTensor, TTensorI, TTensorInd, TDirectSum, TInplace);

#F TGrp(<s>)
#F  tSPL grouping container (i.e. parenthesis)
#F
Class(TGrp, Tagged_tSPL_Container, rec(
    abbrevs :=  [ s -> Checked(IsSPL(s), [s]) ],
    HashId := self >> let(
	p := self.params[1],
	h := When(IsBound(p.HashId), p.HashId(), p),
        [ TGrp, h ] :: When(IsBound(self.tags), self.tags, [])
    )
));

#F TTensor(<A>, <B>)
#F    Nonterminal for Tensor(A, B)
#F
Class(TTensor, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (A, B) -> Checked(IsNonTerminal(A), IsNonTerminal(B), [A,B]) ],

    dims := self >> let(
	a := self.params[1].dims(), b := self.params[2].dims(),
	[ a[1]*b[1], a[2]*b[2] ] ),

    terminate := self >> Tensor(self.params[1], self.params[2]),

    transpose := self >>
        TTensor(self.params[1].transpose(), self.params[2].transpose())
           .withTags(self.getTags()),

    isReal := self >> self.params[1].isReal() and self.params[2].isReal(),

    normalizedArithCost := self >>
        self.params[1].normalizedArithCost() * Rows(self.params[2]) +
	self.params[2].normalizedArithCost() * Cols(self.params[1]),

    HashId := self >> let(
	h := List(self.params, i -> When(IsBound(i.HashId), i.HashId(), i)),
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

#F TDirectSum(<A>, <B>)
#F    Nonterminal for DirectSum(A, B)
#F
Class(TDirectSum, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (A, B) -> Checked(IsSPL(A), IsSPL(B), [ A, B ]) ],
    dims := self >> let(
	a:=self.params[1].dims(), b:=self.params[2].dims(),
	[ a[1]+b[1], a[2]+b[2] ]),

    terminate := self >> DirectSum(self.params[1], self.params[2]),

    transpose := self >>
        TDirectSum(self.params[1].transpose(), self.params[2].transpose())
	    .withTags(self.getTags()),

    isReal := self >> self.params[1].isReal() and self.params[2].isReal(),

    doNotMeasure := true,

    HashId := self >> let(
	h := List(self.params, i->When(IsBound(i.HashId), i.HashId(), i)),
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

#F TTensorI(<nt>, <s>, <l>, <r>)
#F
#F tSPL nonterminal for A x I, I x A, (A x I)L, (I x A)L
#F
#F Example: TTensorI(F(2), 32, APar, APar)
#F
Class(TTensorI, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (nt, s, l, r) -> Checked(
        IsSPL(nt), IsPosInt(s), l in [APar, AVec], r in [APar, AVec],
	[nt, s, l, r])
    ],

    dims := self >> self.params[1].dims()*self.params[2],

    SPLtSPL := (self, nt, P) >> let(
	A := nt[1], n := P[2], l := P[3], r := P[4],
        Cond(IsParPar(P), Tensor(I(n), A),
             IsVecVec(P), Tensor(A, I(n)),
             IsParVec(P), Tensor(I(n), A) * L(A.dims()[2]*n, n),
             IsVecPar(P), let(m:=A.dims()[2], Tensor(A, I(n)) * L(m*n, m))
    )),

    terminate := self >> let(
	P := self.params, A:=P[1].terminate(), n:= P[2], l:=P[3],  r:=P[4],
        Cond(IsParPar(P), Tensor(I(n), A),
             IsVecVec(P), Tensor(A, I(n)),
             IsParVec(P), Tensor(I(n), A) * L(A.dims()[2]*n, n),
             IsVecPar(P), let(m:=A.dims()[2], Tensor(A, I(n)) * L(m*n, m)))),

    transpose := self >> let(p := self.params,
        TTensorI(p[1].transpose(), p[2], p[4], p[3]).withTags(self.getTags())),

    isReal := self >> self.params[1].isReal(),

    normalizedArithCost := self >>
        self.params[1].normalizedArithCost() * self.params[2],

    doNotMeasure := true,

    HashId := self >> let(
	p := self.params,
	h := When(IsBound(p[1].HashId), p[1].HashId(), p[1]),
        [h, p[2], p[3], p[4]] :: When(IsBound(self.tags), self.tags, [])
    ),
));

Class(TTensorInd, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (nt, idx, l, r) -> Checked(
        IsSPL(nt), IsVar(idx), l in [APar, AVec], r in [APar, AVec],
	[nt, idx, l, r])
    ],

    dims := self >> self.params[1].dims() * self.params[2].range,

    SPLtSPL := (self, nt, P) >> let(
	A := nt[1], idx := P[2], m := A.dims()[2], n := idx.range,
        dsA := IDirSum(idx, A),
        Cond(IsParPar(P), dsA,
             IsVecVec(P), L(m*n, m) * dsA * L(m*n, n),
             IsParVec(P), dsA * L(m*n, n),
             IsVecPar(P), L(m*n, m) * dsA)
    ),

    terminate := self >> let(
	P := self.params, A := P[1], idx:= P[2],
        lstA := List([0..idx.range-1],
	    i -> SubstVars(Copy(A), rec((idx.id) := V(i))).terminate()),
        d := A.dims(),
	n := idx.range,
        dsA := DirectSum(lstA),
        Cond(IsParPar(P), dsA,
             IsVecVec(P), L(d[1]*n, d[1]) * dsA * L(d[2]*n, n),
             IsParVec(P), dsA * L(m*n, n),
             IsVecPar(P), L(m*n, m) * dsA)
    ),

    transpose := self >> TTensorInd(
       self.params[1].transpose(), self.params[2], self.params[4], self.params[3])
          .withTags(self.getTags()),

    isReal := self >> self.params[1].isReal(),

    normalizedArithCost := self >>
        self.params[1].normalizedArithCost() * self.params[2].range,

#    doNotMeasure := true,

    HashId := self >> let(
	p := self.params,
	h := When(IsBound(p[1].HashId), p[1].HashId(), p[1]),
        [h, p[2].range, p[3], p[4]] :: When(IsBound(self.tags), self.tags, [])
    )
));

Declare(TL);

#F  tSPL I x L x I
#F  TL(m, n, k, j)
#F  I(k), L(m, n), I(j)
Class(TL, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (size, stride) -> Checked(ForAll([size, stride], IsPosIntSym), [size, stride, 1, 1]),
                  (size, stride, left, right) -> Checked(ForAll([size, stride, left, right], IsPosIntSym), [size, stride, left, right]) ],
    __call__ := arg >> let(self := arg[1], args := Drop(arg, 1),
        Cond(args=[1,1,1,1], I(1),
             ApplyFunc(Inherited,args))),

    dims := self >> Replicate(2, self.params[1]*self.params[3]*self.params[4]),
    terminate := self >> Tensor(I(self.params[3]), L(self.params[1], self.params[2]), I(self.params[4])),
    transpose := self >> TL(self.params[1], self.params[1]/self.params[2], self.params[3], self.params[4]).withTags(self.getTags()),
    isReal := self >> true,
    noCodelet := true,
    # need that for DPBench!
    doNotMeasure := true,
    transposeSymmetric := False,
    isSymmetric := self >> self.params[2]^2 = self.params[1],
    hashAs := self >> ObjId(self)(self.params[1], self.params[2], self.params[3], self.params[4]).withTags(self.getTags()),
));

#F  tSPL Bit Reversal
Class(TBR, Tagged_tSPL_Container, rec(
    abbrevs :=  [ n -> Checked(IsInt(n), [n])],
    dims := self >> [self.params[1], self.params[1]],
    terminate := self >> BR(self.params[1]),
    transpose := self >> self,
    isReal := True,
    doNotMeasure := true,
));

Declare(TRDiag);

#F TRDiag(<nrows>, <ncols>, <diagfunc>)
#F
#F TRDiag is a rectangular tall matrix, which is a diagonal
#F zero-padded on the bottom.
#F
#F To obtain wide matrix (diagonal zero-padded on the right),
#F use TRDiag(...).transpose()
#F
#F Example:
#F  spiral> PrintMat(MatSPL(  TRDiag(6, 3, FList(TInt, [1,2,3])) ));
#F  [ [ 1,  ,   ],
#F    [  , 2,   ],
#F    [  ,  , 3 ],
#F    [  ,  ,   ],
#F    [  ,  ,   ],
#F    [  ,  ,   ] ]
#F
Class(TRDiag, Tagged_tSPL, rec(
    abbrevs :=  [ (m,n,diag) -> Checked(IsPosIntSym(m), IsPosIntSym(n),
	    AnySyms(m, n) or (m>=n),
	    IsFunction(diag) or IsFuncExp(diag),
	    [m, n, diag])],

    dims :=  self >> let( d := [ self.params[1], self.params[2] ], Cond(self.transposed, Reversed(d), d)),

    # tells autolib's ConstrainPSums that params[2] must be = params[3].domain()
    # NOTE: get rid of params[2]
    paramConstraints := self >> [[self.params[2], self.params[3].domain()]],

    terminate := self >> let(
        res := RI(self.params[1], self.params[2]) * Diag(self.params[3]),
	When(self.transposed, res.transpose(), res)),

    conjTranspose := self >> CopyFields(self,
	rec(params := [self.params[1], self.params[2], FConj(self.params[3])]))
            .transpose(),

    isReal := self >> not IsComplexT(self.params[3].range()),

    doNotMeasure := true,

    hashAs := self >> let(
	t := ObjId(self)(self.params[1], self.params[2],
	                 fUnk(self.params[3].range(), self.params[3].domain())),
	tt := t.withTags(self.getTags()),
	When(self.transposed, tt.transpose(), tt))
));

#F  tSPL Digit Reversal
Class(TDR, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (n, r) -> Checked(IsInt(n) and  IsInt(r) and n=r^LogInt(n,r), [n, r])],
    dims := self >> [self.params[1], self.params[1]],
    terminate := self >> DR(self.params[1], self.params[2]),
    transpose := self >> self,
    isReal := True,
    doNotMeasure := true,
));

Declare(TReflL);
#F  tSPL Refl(L(mn,m))
Class(TReflL, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (mn, m) -> Checked(IsInt(mn) and  IsInt(m), [mn, m]) ],
    dims := self >> self.terminate().dims(),
    terminate := self >> let(mn := self.params[1], m:=self.params[2], N := mn/2,
                             Refl(N, 2*N-1, N, L(mn, m))),
    transpose := self >> TReflL(self.params[1], self.params[1]/self.params[2]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
));

Declare(TRC, TCompose, TDiag, TICompose);

#F   tSPL RC(.)
Class(TRC, Tagged_tSPL_Container, rec(
    _short_print := true,
    abbrevs :=  [ (A) -> Checked(IsNonTerminal(A) or IsSPL(A), [A]) ],
    dims := self >> 2*self.params[1].dims(),
    terminate := self >> Mat(MatSPL(RC(self.params[1]))),

    transpose := self >> ObjId(self)(
	self.params[1].conjTranspose()).withTags(self.getTags()),

    conjTranspose := self >> self.transpose(),

    isReal := self >> true,

    # Do not use doNotMeasure, this will prevent TRC_By_Def from ever being found!
    doNotMeasure := false,
    normalizedArithCost := self >> self.params[1].normalizedArithCost(),

    HashId := self >> let(
	h := [ When(IsBound(self.params[1].HashId), self.params[1].HashId(),
		    self.params[1]) ],
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

Class(TMat, Tagged_tSPL, rec(
    abbrevs :=  [ m -> Checked(IsMat(m), [m]) ],
    dims := self >> DimensionsMat(self.params[1]),
    terminate := self >> Mat(self.params[1]),
    transpose := self >> ObjId(self)(TransposedMat(self.params[1])).withTags(self.getTags()),
    isReal := self >> IsRealMat(self.params[1]),
    isInplace := self >> false,
    normalizedArithCost := self >> 2 * Rows(self.params[1]) * Cols(self.params[1]),
    doNotMeasure := false,
));

#F   tSPL Diag(d)
Class(TDiag, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (D) -> [D] ],
    dims := self >> let(n:=self.params[1].domain(), [n,n]),
    terminate := self >> Diag(self.params[1]),
    transpose := self >> Copy(self),
    conjTranspose := self >> ObjId(self)(FConj(self.params[1])).withTags(self.getTags()),

    isReal := self >> Diag(self.params[1]).isReal(),

    doNotMeasure := true,
    noCodelet := true,
    hashAs := self >> ObjId(self)(FUnk(self.params[1].domain())).withTags(self.getTags()),
    #NOTE: depends on wether the whole transform is complex...
    normalizedArithCost := self >> When(self.params[1].isReal(), 2*Rows(self), 6*Rows(self))
));

#F   tSPL RCDiag(d)
Class(TRCDiag, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (D) -> [D] ],
    dims := self >> let(n:=self.params[1].domain(), [n,n]),
    terminate := self >> RCDiag(self.params[1]),
    # NOTE: transpose on real == conjugate; transpose on complex == id. What to use?
#    transpose := self >> let(d := self.params[1].var.value, t := d.t.t, size := d.t.size,
#        newd := List(d.v, i-> t.value(Complex(ReComplex(i.v), -ImComplex(i.v)))),
#        FData(newd)),
    transpose := self >> CopyFields(self, rec(transposed := not self.transposed)),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    hashAs := self >> ObjId(self)(FUnk(self.params[1].domain())).withTags(self.getTags()),
    normalizedArithCost := self >> 3*Rows(self)
));

#F   tSPL Id(f)
Class(TId, TDiag);

Declare(TCond);
#F   tSPL Cond
Class(TCond, Tagged_tSPL_Container, rec(
    abbrevs   := [ (f, a, b) -> Checked(IsBound(f.ev), [f,a,b]) ],
    dims      := self >> self.params[2].dims(),
    terminate := self >> COND(self.params[1], self.params[2].terminate(), self.params[3].terminate()),
    transpose := self >> TCond(self.params[1], self.params[2].transpose(), self.params[3].transpose()), # maybe
));

Declare(TRaderMid);
#F   tSPL RaderMid(N, k, root, transp)
Class(TRaderMid, Tagged_tSPL_Container, rec(
    # -- Rader infrastructure --
    raderDiag := (N, k, root) -> SubList(ComplexFFT(List([0..N-2], x->ComplexW(N, k*root^x mod N))), 2) / (N-1),
    raderMid := (self, N, k, root) >>
                 DirectSum(Mat([[1, 1], [1, -1/(N-1)]]),
                   Diag(FData(self.raderDiag(N, k, root)))),
    # -- tSPL infrastructure --
    abbrevs :=  [ (N, k, root) -> [N, k, root, false],
                  (N, k, root, transp) -> [N, k, root, transp] ],
    dims := self >> let(N:=self.params[1], [N,N]),
    terminate := self >> let(spl := self.raderMid(self.params[1], self.params[2], self.params[3]),
        When(self.params[4], spl.transpose(), spl)),
    transpose := self >> TRaderMid(self.params[1], self.params[2], self.params[3], not self.params[4]).withTags(self.getTags()),
    isReal := False,
    doNotMeasure := true,
    noCodelet := true,
));

#F   tSPL Compose([A,B,...])
Class(TCompose, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (l) -> Checked(IsList(l), [l]) ],
    dims := self >> [self.params[1][1].dims()[1], self.params[1][Length(self.params[1])].dims()[2]],
    terminate := self >> Compose(List(self.params[1], i->i.terminate())),
    transpose := self >> TCompose(Reversed(List(self.params[1], i->i.transpose()))).withTags(self.getTags()),
    isReal := self >> ForAll(self.params[1], i->i.isReal()),
    doNotMeasure := true,
    normalizedArithCost := self >> Sum(List(self.params[1], i->i.normalizedArithCost())),

    HashId := self >> let(h := List(self.params[1], i->When(IsBound(i.HashId), i.HashId(), i)),
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

#F   tSPL ICompose(<var>, <domain>, <nonterm>, <attrib>)
Class(TICompose, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (var,domain,l) -> [var, domain, l] ],
    dims := self >> self.params[3].dims(),
    terminate := self >> ICompose(self.params[1], self.params[2], self.params[3]).unroll(),

    unroll := self >> let(
        var := self.params[1],
        domain := self.params[2],
        expr := self.params[3],
        tag := self.getTags(),
        TCompose(
            List([ 0 .. domain - 1 ],
                index_value -> SubstBottomUp(
                    Copy(expr), var,
                    e -> V(index_value))))),

    isReal := self >> ICompose(self.params[1], self.params[2], self.params[3]).unroll().isReal(),

    transpose := self >> let(
        var := self.params[1],
        dom := self.params[2],
        ch := self.params[3],
        newvar := var.clone(),
        tags := self.getTags(),
        TICompose(newvar, dom,
            SubstVars(Copy(ch.transpose()), tab((var.id) := dom-1-newvar))).withTags(tags)
    )
));

Declare(TGath, TScat, TPrm, TConj, TS);
#F   tSPL Gath(d)
Class(TGath, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> [self.params[1].domain(), self.params[1].range()],
    terminate := self >> Gath(self.params[1]),
    transpose := self >> TScat(self.params[1]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

Class(TScat, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> [self.params[1].range(),self.params[1].domain()],
    terminate := self >> Scat(self.params[1]),
    transpose := self >> TGath(self.params[1]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

Class(TPrm, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> Cond(IsBound(self.params[1].domain), [self.params[1].domain(), self.params[1].range()], self.params[1].dims()),
    terminate := self >> Prm(self.params[1]),
    transpose := self >> TPrm(self.params[1].transpose()).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

Class(TConj, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl, prm) -> [tspl,prm.transpose(),prm],
                  (tspl, prml, prmr) -> [tspl,prml,prmr] ],
    dims := self >> [self.params[2].domain(), self.params[3].range()],
    terminate := self >> ConjLR(self.params[1].terminate(), self.params[2], self.params[3]),
    transpose := self >> TConj(self.params[1].transpose(), self.params[3].transpose(), self.params[2].transpose()).withTags(self.getTags()),
    isReal := self >> self.params[1].isReal(),
    doNotMeasure := false,
    noCodelet := true,
    normalizedArithCost := self >> self.params[1].normalizedArithCost()
));

Class(TS, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (N) -> [N, false],
                  (N, t) -> [N, t] ],
    dims := self >> [self.params[1], self.params[1]],
    terminate := self >> When(self.params[2], S(self.params[1]).transpose(), S(self.params[1])),
    transpose := self >> TS(self.params[1], not self.params[2]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> self.params[1] - 1
));

Class(TInplace, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> self.params[1].dims(),
    terminate := self >> self.params[1].terminate(),
    transpose := self >> TInplace(self.params[1].transpose()).withTags(self.getTags()),
    isReal := self >> self.params[1].isReal(),
    isInplace := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> self.params[1].normalizedArithCost()
));

InterleavedComplexT := t -> TRC(t);
SplitComplexT := t -> let(r:= Rows(t), c:=Cols(t), TConj(TRC(t), L(2*r, 2), L(2*c, c)));
ComplexT := (t,opts) -> When((not IsBound(opts.interleavedComplex)) or (IsBound(opts.interleavedComplex) and opts.interleavedComplex), InterleavedComplexT(t), SplitComplexT(t));

InterleavedSplitComplexT := t -> let(r:= Rows(t), c:=Cols(t), TConj(TRC(t), fId(2*r), L(2*c, c)));


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

NewRulesFor(TFCall, rec(
    TFCall_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> c[1]
    )
));

NewRulesFor(TDeviceCall, rec(
    TFCall_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> c[1]
    )
));

NewRulesFor(TSparseMat, rec(
    TSparseMat_base := rec(
        forTransposition := false,
        children := nt -> [[ ]],
        applicable := (self, nt) >> When(IsBound(self.max_rows), nt.params[1][1] <= self.max_rows, true),
        apply := function(nt, c, cnt)
            local P, mat, i, j, rdata;
            P := nt.params;
            mat := MatSPL(ApplyFunc(O, P[1]));

            for i in [0..P[1][1]-1] do
                rdata := Drop(Filtered(P[2], ii->ii[1] = i)[1], 1);
                for j in rdata do
                    mat[i+1][j[1]+1] := j[2];
                od;
            od;

            return Mat(mat);
        end
    ),
    TSparseMat_VStack := rec(
        forTransposition := false,
        applicable := nt -> nt.params[1][1] > 1,
        children := nt -> [ List([0..nt.params[1][1]-1], i->TSparseMat([1, nt.params[1][2]], [let(row := Filtered(nt.params[2], j->j[1] = i)[1], [0] :: Drop(row, 1))])) ],
        apply := (nt, c, cnt) -> BB(VStack(c))
    ),
));

NewRulesFor(TIterHStack, rec(
    TIterHStack_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()), InfoNt(nt.params[2]) ]],
        apply := (nt, c, cnt) -> IterHStack(cnt[2].params[1], cnt[2].params[1].range, c[1])
    )
));

NewRulesFor(TIterVStack, rec(
    TIterVStack_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()), InfoNt(nt.params[2]) ]],
        apply := (nt, c, cnt) -> IterVStack(cnt[2].params[1], cnt[2].params[1].range, c[1])
    )
));

NewRulesFor(TNoDiagPullinLeft, rec(
    TNoDiagPullinLeft_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoDiagPullinLeft(c[1])
    )
));

NewRulesFor(TNoDiagPullinRight, rec(
    TNoDiagPullinRight_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoDiagPullinRight(c[1])
    )
));

NewRulesFor(TNoPullLeft, rec(
    TNoPullLeft_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoPullLeft(c[1])
    )
));

NewRulesFor(TNoPullRight, rec(
    TNoPullRight_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoPullRight(c[1])
    )
));

NewRulesFor(TColMajor, rec(
    TColMajor_MDDFT_2D := rec(
        forTransposition := false,
        switch := false,
        applicable := (self, nt) >> ObjId(nt.params[1]) = MDDFT and Length(nt.params[1].params[1]) = 2,

        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> let(n1 := nt.params[1].params[1][1], n2 := nt.params[2][1].params[1][2],
            prm := L(n1*n2, n2),
            iprm := prm.transpose(),
            iprm * c[1] * prm)
    ),
    TColMajor_MDDFT_3D := rec(
        forTransposition := false,
        switch := false,
        applicable := (self, nt) >> ObjId(nt.params[1]) = MDDFT and Length(nt.params[1].params[1]) = 3,

        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> let(n1 := nt.params[1].params[1][1], n2 := nt.params[1].params[1][2], n3 := nt.params[1].params[1][3],
            # prm := Tensor(I(n1), L(n2*n3, n3)) * L(n1*n2*n3, n2*n3),
            prm := Prm(fCompose(L(n1*n2*n3, n2*n3), fTensor(fId(n1), L(n2*n3, n3)))),
            # iprm := L(n1*n2*n3, n1) * Tensor(I(n1), L(n2*n3, n2)),
            iprm := prm.transpose(),
            iprm * c[1] * prm)
    ),
    TColMajor_MDDFT_tensorFlip := rec(
        forTransposition := false,
        applicable := (self, nt) >> ObjId(nt.params[1]) = MDDFT,

        children := nt -> [[ ApplyFunc(MDDFT, [Reversed(nt.params[1].params[1])]::Drop(nt.params[1].params, 1)).withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> c[1]
    ),
));