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

#
# SSE conversion bridges
#

# simple cases - signed<->unsigned

Class(_cvt_int_uint_saturated, ISA_Bridge_I, rec(
    props := ["saturation"],
    code  := (self, y, x, opts) >>
        assign( self._y(y,0), tcast(self.isa_to.t, bin_and(self._x(x,0), mask_gt(self._x(x,0), self.isa_from.t.zero()))) )
));

ISA_Bridge.add(Class(CVT_SSE_4x32ui_4x32i_wrap, ISA_Bridge_tcast, rec(
    isa_from := SSE_4x32f(T_Int(32)),   isa_to := SSE_4x32f(T_UInt(32)),
)));

ISA_Bridge.add(Class(CVT_SSE_4x32ui_4x32i_sat, _cvt_int_uint_saturated, rec(
    isa_from := SSE_4x32f(T_Int(32)),   isa_to := SSE_4x32f(T_UInt(32))
)));

ISA_Bridge.add(Class(CVT_SSE_4x32i_4x32ui_wrap, ISA_Bridge_tcast, rec(
    isa_from := SSE_4x32f(T_UInt(32)),  isa_to := SSE_4x32f(T_Int(32)),
)));

ISA_Bridge.add(Class(CVT_SSE_8x16ui_8x16i_wrap, ISA_Bridge_tcast, rec(
    isa_from := SSE_8x16i(T_Int(16)),   isa_to := SSE_8x16i(T_UInt(16)),
)));

ISA_Bridge.add(Class(CVT_SSE_8x16ui_8x16i_sat, _cvt_int_uint_saturated, rec(
    isa_from := SSE_8x16i(T_Int(16)),   isa_to := SSE_8x16i(T_UInt(16))
)));

ISA_Bridge.add(Class(CVT_SSE_8x16i_8x16ui_wrap, ISA_Bridge_tcast, rec(
    isa_from := SSE_8x16i(T_UInt(16)),  isa_to := SSE_8x16i(T_Int(16)),
)));

ISA_Bridge.add(Class(CVT_SSE_16x8ui_16x8i_wrap, ISA_Bridge_tcast, rec(
    isa_from := SSE_16x8i(T_Int(8)),    isa_to := SSE_16x8i(T_UInt(8)),
)));

ISA_Bridge.add(Class(CVT_SSE_16x8ui_16x8i_sat, _cvt_int_uint_saturated, rec(
    isa_from := SSE_16x8i(T_Int(8)),    isa_to := SSE_16x8i(T_UInt(8)),
)));

ISA_Bridge.add(Class(CVT_SSE_16x8i_16x8ui_wrap, ISA_Bridge_tcast, rec(
    isa_from := SSE_16x8i(T_UInt(8)),   isa_to := SSE_16x8i(T_Int(8)),
)));

# more complex cases

ISA_Bridge.add(Class(CVT_SSE_4x32i_4x32f_trunc, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_Real(32)),
    isa_to      := SSE_4x32f(T_Int(32)),
    props       := ["trunc"],
    code := (self, y, x, opts) >> assign( self._y(y,0), vcvtt_4x32_f2i(self._x(x,0)) ),
)));

ISA_Bridge.add(Class(CVT_SSE_4x32i_4x32f_round, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_Real(32)),
    isa_to      := SSE_4x32f(T_Int(32)),
    props       := ["round"],
    code := (self, y, x, opts) >> assign( self._y(y,0), vcvt_4x32_f2i(self._x(x,0)) ),
)));

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

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

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

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

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

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

ISA_Bridge.add(Class(CVT_SSE_8x16i_4x32i_sat, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_Int(32)),
    isa_to      := SSE_8x16i(T_Int(16)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> assign( self._y(y,0), vpacks_4x32i(self._x(x,0), self._x(x,1)) )
)));

# saturated SSE_4x32i to SSE_8x16ui
ISA_Bridge.add(Class(CVT_SSE_8x16ui_4x32i_sat, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_Int(32)),
    isa_to      := SSE_8x16i(T_UInt(16)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> let(
                c1 := self.isa_from.t.value(32768),
                c2 := self.isa_to.t.value(32768),
                u1 := var.fresh_t("U", self.isa_from.t),
                u2 := var.fresh_t("U", self.isa_from.t),
                m1 := var.fresh_t("U", self.isa_from.t),
                m2 := var.fresh_t("U", self.isa_from.t),
                decl([u1, u2, m1, m2], chain(
                    assign( u1, self._x(x,0) ),
                    assign( u2, self._x(x,1) ),
                    assign( m1, sub(bin_and(u1, mask_lt(u1.t.zero(), u1)), c1)),
                    assign( m2, sub(bin_and(u2, mask_lt(u1.t.zero(), u2)), c1)),
                    assign( self._y(y,0), add(tcast(self.isa_to.t, vpacks_4x32i(m1, m2)), c2) )
                ))
            ),
)));

# saturated SSE_4x32ui to SSE_8x16ui
ISA_Bridge.add(Class(CVT_SSE_8x16ui_4x32ui_sat, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_UInt(32)),
    isa_to      := SSE_8x16i(T_UInt(16)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> let(
                c1 := self.isa_from.t.value(32768),
                c2 := self.isa_to.t.value(32768),
                m1 := var.fresh_t("U", self.isa_from.t),
                m2 := var.fresh_t("U", self.isa_from.t),
                decl([m1, m2], chain(
                    assign( m1, sub(self._x(x,0), c1)),
                    assign( m2, sub(self._x(x,1), c1)),
                    assign( self._y(y,0), add(tcast(self.isa_to.t, vpacks_4x32i(m1, m2)), c2) )
                ))
            ),
)));

# SSE_4x32i to SSE_8x16ui without saturation by throwing out high word.
ISA_Bridge.add(Class(CVT_SSE_8x16ui_4x32i_wrap, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_Int(32)),
    isa_to      := SSE_8x16i(T_UInt(16)),
    props       := ["wraparound"],
    code := (self, y, x, opts) >> let(
                u1 := var.fresh_t("U", self.isa_to.t),
                u2 := var.fresh_t("U", self.isa_to.t),
                m1 := var.fresh_t("U", self.isa_to.t),
                m2 := var.fresh_t("U", self.isa_to.t),
                m3 := var.fresh_t("U", self.isa_to.t),
                m4 := var.fresh_t("U", self.isa_to.t),
                decl([u1, u2, m1, m2, m3, m4], chain(
                    assign( u1, tcast(u1.t, self._x(x,0)) ),
                    assign( u2, tcast(u2.t, self._x(x,1)) ),
                    assign( m1, vunpacklo_8x16i(u1, u2) ),
                    assign( m2, vunpackhi_8x16i(u1, u2) ),
                    assign( m3, vunpacklo_8x16i(m1, m2) ),
                    assign( m4, vunpackhi_8x16i(m1, m2) ),
                    assign( self._y(y,0), vunpacklo_8x16i(m3, m4) )
                ))
            ),
)));

ISA_Bridge.add(Class(CVT_SSE_16x8i_8x16i_sat, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_Int(16)),
    isa_to      := SSE_16x8i(T_Int(8)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> assign( self._y(y,0), vpacks_8x16i(self._x(x,0), self._x(x,1)) )
)));

ISA_Bridge.add(Class(CVT_SSE_16x8ui_8x16i_sat, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_Int(16)),
    isa_to      := SSE_16x8i(T_UInt(8)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> assign( self._y(y,0), vpackus_8x16i(self._x(x,0), self._x(x,1)) )
)));

ISA_Bridge.add(Class(CVT_SSE_16x8ui_8x16ui_sat_as, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_UInt(16)),
    isa_to      := SSE_16x8i(T_UInt(8)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> let(
                t  := TVect(T_Int(16), 8),
                a1 := var.fresh_t("U", t),
                a2 := var.fresh_t("U", t),
                m1 := var.fresh_t("U", self.isa_to.t),
                m2 := var.fresh_t("U", self.isa_to.t),
                decl([a1,a2,m1,m2], chain(
                    assign( a1, arith_shr(tcast(t, self._x(x,0)), 15) ),
                    assign( a2, arith_shr(tcast(t, self._x(x,1)), 15) ),
                    assign( m1, vpacks_8x16i(a1, a2) ),
                    assign( m2, vpackus_8x16i(self._x(x,0), self._x(x,1)) ),
                    assign( self._y(y,0), bin_or(m1,m2) )
                ))
            ),
)));

ISA_Bridge.add(Class(CVT_SSE_16x8ui_8x16ui_sat_gt, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_UInt(16)),
    isa_to      := SSE_16x8i(T_UInt(8)),
    props       := ["saturation"],
    code := (self, y, x, opts) >> let(
                t  := TVect(T_Int(16), 8),
                a1 := var.fresh_t("U", t),
                a2 := var.fresh_t("U", t),
                m1 := var.fresh_t("U", self.isa_to.t),
                m2 := var.fresh_t("U", self.isa_to.t),
                decl([a1,a2,m1,m2], chain(
                    assign( a1, gt(t.zero(), tcast(t, self._x(x,0))) ),
                    assign( a2, gt(t.zero(), tcast(t, self._x(x,1))) ),
                    assign( m1, vpacks_8x16i(a1, a2) ),
                    assign( m2, vpackus_8x16i(self._x(x,0), self._x(x,1)) ),
                    assign( self._y(y,0), bin_or(m1,m2) )
                ))
            ),
)));

ISA_Bridge.add(Class(CVT_SSE_8x16i_16x8i_gt, ISA_Bridge_I, rec(
    isa_from    := SSE_16x8i(T_Int(8)),
    isa_to      := SSE_8x16i(T_Int(16)),
    props       := [],
    code := (self, y, x, opts) >> let(
                sign := var.fresh_t("U", self.isa_from.t),
                decl( [sign], chain(
                    assign(sign, gt(self.isa_from.t.zero(), self._x(x,0))),
                    assign(self._y(y,0), tcast( self.isa_to.t, vunpacklo_16x8i(self._x(x,0), sign)) ),
                    assign(self._y(y,1), tcast( self.isa_to.t, vunpackhi_16x8i(self._x(x,0), sign)) )
                ))
            ),
)));

ISA_Bridge.add(Class(CVT_SSE_8x16i_16x8i_as, ISA_Bridge_I, rec(
    isa_from    := SSE_16x8i(T_Int(8)),
    isa_to      := SSE_8x16i(T_Int(16)),
    props       := [],
    code := (self, y, x, opts) >> chain(
                    assign(self._y(y,0), arith_shr(tcast( self.isa_to.t, vunpacklo_16x8i(self._x(x,0), self._x(x,0))), 8) ),
                    assign(self._y(y,1), arith_shr(tcast( self.isa_to.t, vunpackhi_16x8i(self._x(x,0), self._x(x,0))), 8) )
                ),
)));

ISA_Bridge.add(Class(CVT_SSE_8x16i_16x8ui, ISA_Bridge_I, rec(
    isa_from    := SSE_16x8i(T_UInt(8)),
    isa_to      := SSE_8x16i(T_Int(16)),
    props       := [],
    code := (self, y, x, opts) >> chain(
                    assign(self._y(y,0), vcastizxlo(self._x(x,0)) ),
                    assign(self._y(y,1), vcastizxhi(self._x(x,0)) )
                )
)));

ISA_Bridge.add(Class(CVT_SSE_4x32i_8x16i_gt, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_Int(16)),
    isa_to      := SSE_4x32f(T_Int(32)),
    props       := [],
    code := (self, y, x, opts) >> let(
                sign := var.fresh_t("U", self.isa_from.t),
                decl( [sign], chain(
                    assign(sign, gt(self.isa_from.t.zero(), self._x(x,0))),
                    assign(self._y(y,0), tcast( self.isa_to.t, vunpacklo_8x16i(self._x(x,0), sign)) ),
                    assign(self._y(y,1), tcast( self.isa_to.t, vunpackhi_8x16i(self._x(x,0), sign)) )
                ))
            ),
)));

ISA_Bridge.add(Class(CVT_SSE_4x32i_8x16i_as, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_Int(16)),
    isa_to      := SSE_4x32f(T_Int(32)),
    props       := [],
    code := (self, y, x, opts) >> chain(
                    assign(self._y(y,0), arith_shr(tcast( self.isa_to.t, vunpacklo_8x16i(self._x(x,0), self._x(x,0))), 16) ),
                    assign(self._y(y,1), arith_shr(tcast( self.isa_to.t, vunpackhi_8x16i(self._x(x,0), self._x(x,0))), 16) )
                )
)));

ISA_Bridge.add(Class(CVT_SSE_4x32i_8x16ui, ISA_Bridge_I, rec(
    isa_from    := SSE_8x16i(T_UInt(16)),
    isa_to      := SSE_4x32f(T_Int(32)),
    props       := [],
    code := (self, y, x, opts) >> chain(
                    assign(self._y(y,0), tcast( self.isa_to.t, vunpacklo_8x16i(self._x(x,0), self.isa_from.t.zero())) ),
                    assign(self._y(y,1), tcast( self.isa_to.t, vunpackhi_8x16i(self._x(x,0), self.isa_from.t.zero())) )
                )
)));

ISA_Bridge.add(Class(CVT_SSE_4x32f_4x32i, ISA_Bridge_I, rec(
    isa_from    := SSE_4x32f(T_Int(32)),
    isa_to      := SSE_4x32f(T_Real(32)),
    props       := [],
    code := (self, y, x, opts) >> assign(self._y(y,0), vcvt_4x32_i2f(self._x(x,0)))
)));


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

Declare(TPrmMulti,CodeBlock);
IsTwoPower := i >> 2 ^ Log2Int(i) = i;

get_l_power := function(t,exp)
  local p;
  if (exp=0) then
    p := L(2^t,1);
  else
    p:= L(2^t,2^(t-exp));
  fi;
  return p;
end;

# Declaration of SortBase, a 2x2 sorter.
Class(SortBase, BaseMat, rec(
   abbrevs   := [()-> []],
   new       := (self) >> SPL( WithBases(self, rec()) ).setDims(),
   dims      := self >> [ 2, 2 ],
   isReal    := True,
   sums     := self >> self,
   rChildren := self >> [],
   rSetChild := rSetChildFields(),
   toAMat := self >> Error("not supported"),
   transpose := self >> self,
));

# Declaration of SortBase, a 1x1 sorter.
Class(SortBase_w1, BaseMat, rec(
   abbrevs   := [(a)-> [a]],
   new       := (self, a) >> SPL( WithBases(self, rec(dimensions:=[1,1], a := a))),
   print := (self, i, is) >> Print(self.name, "(", self.a, ")"),
   dims      := self >> [ 1, 1 ],
   isReal    := True,
   sums     := self >> self,
   rChildren := self >> [self.a],
   rSetChild := rSetChildFields("a"),
   toAMat := self >> Error("not supported"),
   transpose := self >> self,
));

Class(SortConfigBase_w1, BaseMat, rec(
   abbrevs   := [(a)-> [a] , (b)-> [b]],
   new       := (self, a, b) >> SPL( WithBases(self, rec(dimensions:=[1,1], a := a, b:=b))),
   print := (self, i, is) >> Print(self.name, "(", self.a, ",", self.b, ")"),
   dims      := self >> [ 1, 1 ],
   isReal    := True,
   sums     := self >> self,
   rChildren := self >> [self.a, self.b],
   rSetChild := rSetChildFields("a","b"),
   toAMat := self >> Error("not supported"),
   transpose := self >> self,
));

# Declaration of SortConfigBase, a 2x2 Configurable sorter.
Class(SortConfigBase, BaseMat, rec(
   abbrevs   := [(a)-> [a]],
   new       := (self, a) >> SPL( WithBases(self, rec(dimensions:=[2,2], a := a))),
   print := (self, i, is) >> Print(self.name, "(", self.a, ")"),
   dims      := self >> [ 2, 2 ],
   isReal    := True,
   sums     := self >> self,
   rChildren := self >> [self.a],
   rSetChild := rSetChildFields("a"),
   toAMat := self >> Error("not supported"),
   transpose := self >> self,
));

# Declaration of SortLinearBase, used for linear sorter with w = 1
Class(LinearSortBase, BaseMat, rec(
   abbrevs   := [()-> []],
   new       := (self) >> SPL( WithBases(self, rec()) ).setDims(),
   dims      := self >> [ 2, 2 ],
   isReal    := True,
   sums     := self >> self,
   rChildren := self >> [],
   rSetChild := rSetChildFields(),
   toAMat := self >> Error("not supported"),
   transpose := self >> self,
));

#taken from: .../spiral/compiler/dag.gi
regassign.op_in  := self >> ConcatList(self.loc.rChildren(), ArgsExp) :: ArgsExp(self.exp);
regassign.op_out := self >> [self.loc];
regassign.op_inout := self >> [];
regassign.getNoScalar := self >> When(IsBound(self.exp.getNoScalar), self.exp.getNoScalar(), []);

# This tells Spiral how to translate 'SortBase' into code.
HDLCodegen.SortBase := (self, o, y, x, opts) >>
    chain(
	assign(nth(y,0), cond(leq(nth(x,0), nth(x,1)), nth(x,0), nth(x,1))),
	assign(nth(y,1), cond(leq(nth(x,0), nth(x,1)), nth(x,1), nth(x,0)))
    );

HDLCodegen.SortBase_w1 := (self, o, y, x, opts) >>
   let(
     t0 := TempVar(x.t.t),
     t1 := TempVar(x.t.t),
     t3 := TempVar(x.t.t),
     t5 := TempVar(x.t.t),
     t6 := TempVar(x.t.t),

     chain(
	assign(t3, imod(o.a, 2)),
	regassign(t0, cond(t3, t0, nth(x,0))),
	assign(t5, cond(leq(t0, nth(x,0)), nth(x,0), t0)),
	assign(t6, cond(leq(t0, nth(x,0)), t0, nth(x,0))),
	regassign(t1, cond(t3, t5, t1)),
	assign(nth(y,0), cond(t3, t6, t1))
     )
    );

HDLCodegen.SortConfigBase_w1 := (self, o, y, x, opts) >>
   let(
     t0 := TempVar(x.t.t),
     t1 := TempVar(x.t.t),
     t3 := TempVar(x.t.t),
     t5 := TempVar(x.t.t),
     t6 := TempVar(x.t.t),
     t7 := TempVar(x.t.t),
     t8 := TempVar(x.t.t),
     t9 := TempVar(x.t.t),
     t10 := TempVar(x.t.t),
     t2 := TempVar(x.t.t),

     chain(
	assign(t3, imod(o.a, 2)),
	regassign(t0, cond(t3, t0, nth(x,0))),
	assign(t7, eq(o.b,0)),
	assign(t8, eq(o.b,1)),

	assign(t2, leq(t0, nth(x,0))),
	assign(t9, cond(t2, t0, nth(x,0))),
	assign(t10, cond(t2, nth(x,0), t0)),
	assign(t5, cond(t7, nth(x,0) , t8, t9, t10)),
	assign(t6, cond(t7, t0 , t8, t10, t9)),

	regassign(t1, cond(t3, t5, t1)),
	assign(nth(y,0), cond(t3, t6, t1))
     )
    );

HDLCodegen.LinearSortBase := (self, o, y, x, opts) >>
   let(
     #x[1] indicates the start of a new list. When x[1] equals 1, y[0]
     #is set to the value of the register t0, and t0 is set to the value of x[0]

     t0 := TempVar(x.t.t),
     t1 := TempVar(x.t.t),
     t2 := TempVar(x.t.t),
     t3 := TempVar(x.t.t),
     t4 := TempVar(x.t.t),
     t5 := TempVar(x.t.t),
     r0 := TempVar(x.t.t),
     x0 := TempVar(x.t.t),
     x1 := TempVar(x.t.t),

     chain(

	 assign(x0, nth(x,0)),
	 assign(x1, nth(x,1)),
	 assign(t3, eq(x1, 1)),
	 regassign(r0, cond(t3, x0, t1)),
	 #regassign(r0, t0),
	 assign(t4, leq(r0, x0)),
	 assign(t1, cond(t4, x0, r0)),
	 assign(t2, cond(t4, r0, x0)),
	 assign(nth(y, 0), cond(t3, r0, t2)),
	 #regassign(t5, x1),
	 assign(nth(y, 1), x1)
     )
    );

# HDLCodegen.LinearSortBase := (self, o, y, x, opts) >>
#    let(
#      t0 := TempVar(x.t.t),
#      #x[1] indicates the start of a new list. When x[1] equals 1, y[0]
#      #is set to the value of the register t0, and t0 is set to the value of x[0]
#      chain(
# 	assign(nth(y,0),cond(eq(nth(x,1),1),t0,leq(t0, nth(x,0)),t0,nth(x,0))),
# 	regassign(t0,cond(eq(nth(x,1),1),nth(x,0),leq(t0, nth(x,0)),nth(x,0),t0)),
# 	assign(nth(y,1),nth(x,1))
#      )
#     );

HDLCodegen.SortConfigBase := (self, o, y, x, opts) >>
    let(
	t0 := TempVar(x.t.t),
	t1 := TempVar(x.t.t),
	t2 := TempVar(x.t.t),
	t3 := TempVar(x.t.t),
	chain(
	    assign(t2, nth(x,0)),
	    assign(t3, nth(x,1)),
	    assign(t0, cond(leq(t2, t3), t2, t3)),
	    assign(t1, cond(leq(t2, t3), t3, t2)),
	    assign(nth(y,0), cond(eq(o.a,0), t2, eq(o.a,1), t1, t0)),
	    assign(nth(y,1), cond(eq(o.a,0), t3, eq(o.a,1), t0, t1))
	)
    );

# Declaration of a sorter of size n
Class(Sort, TaggedNonTerminal, rec(
    abbrevs := [
    (n)       -> Checked(IsPosIntSym(n), [_unwrap(n)]),
    ],

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

    dims := self >> [ self.params[1], self.params[1] ],

    terminate := self >> Error("not supported"), # we could probably support this
));

# SortIJPerm(n): DirectSum(I(n/2), J(n/2))
# We do this so we can define a .permBits() function.
# This will let us easily generate large-size hardware implementations.

# (If we do not do this, the perm tool will have to manually compute the
# bit matrix representation, which will include making an n-times-n matrix.
Class(SortIJPerm, PermClass, rec(
    def := (n) -> Checked(
        IsPosIntSym(n),
        rec(size := n)),

    lambda := self >> let(
        n := self.params[1],
	fDirsum(fId(n/2), J(n/2)).lambda()
    ),

    transpose := self >> self,
    isSymmetric := self >> true,

    permBits := meth(self)
        local n, k, a, b, tmp, i;
	n := self.params[1];
	k := LogInt(n, 2);
	a := [List([1..k], i->0)];
	for i in [1 .. k-1] do
	    tmp := Concatenation([1], List([1..k-1], i->0));
	    Append(a, [tmp]);
        od;
	a := a * GF(2).one;
	b := MatSPL(I(k))*GF(2).one;

	return (a+b);
    end,
));

# Rule to breakdown Sort(n), where n is a power of two.
NewRulesFor(Sort, rec(
    Sort_Stream := rec(
        info         := "Streaming sorting network",

        applicable   := nt -> Length(nt.params) = 1 and IsTwoPower(nt.params[1]),

        children := (self, nt) >> let(

	    tag_w_tmp := nt.tags[1],
	    tag_w := tag_w_tmp.bs,
	    t := Log2Int(nt.params[1]),
	    p := Ind(2^t),
	    get_bb := w -> Cond(w=1, TTensorInd(SortBase_w1(p), p, APar, APar),TTensorI(SortBase(), 2^(t-1), APar, APar)),

	    [[ TCompose(
	           [TCompose(List([1..t-1], i ->
	               TCompose([
		           #TTensorI(SortBase(), 2^(t-1), APar, APar),
		           get_bb(tag_w),
		           TCompose(List([2..(t-i+1)], j ->
		               TCompose([
			           TTensorI(TPrm(Tensor(I(2), L(2^(j-1), 2^(j-2))) * L(2^j,2)), 2^(t-j), APar, APar),
			           get_bb(tag_w)
			           #TTensorI(SortBase(), 2^(t-1), APar, APar)
			       ])
		           )),
		        #   TTensorI(TPrm(L(2^(t-i+1), 2^(t-i)) * DirectSum(I(2^(t-i)), J(2^(t-i)))  ), 2^(i-1), APar, APar)
			   TTensorI(TPrm(L(2^(t-i+1), 2^(t-i)) * SortIJPerm(2^(t-i+1))), 2^(i-1), APar, APar)
		       ])
	            )),
		    get_bb(tag_w)]
		    #TTensorI(SortBase(), 2^(t-1), APar, APar)]
                ).withTags(nt.getTags())
            ]]
	),

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

    ),

   Sort_Stream6 := rec(
        info         := "Version of Sort_Stream (sortAlg1) removing J permutations and with configurable 2-input sorters",

        applicable   := nt -> Length(nt.params) = 1 and IsTwoPower(nt.params[1]),

        children := (self, nt) >> let(
	    t := Log2Int(nt.params[1]),
            k := Ind(2^(t-1)),
            z := (s) >> t-s,

            c1 := (s) >> logic_and(eq(bit_sel(k, z(s)), 1), neq(s, 1)),
            access_f := (s) >> cond(c1(s), 1, 2),

	    [[ TCompose(
	           [TCompose(List([1..t-1], i ->
	               TCompose([
			   TTensorInd(SortConfigBase(access_f(i)), k, APar, APar),
		           TCompose(List([2..(t-i+1)], j ->
		               TCompose([
			           TTensorI(TPrm(Tensor(I(2), L(2^(j-1), 2^(j-2))) * L(2^j,2)), 2^(t-j), APar, APar),
			           TTensorInd(SortConfigBase(access_f(i)), k, APar, APar)
			       ])
		           )),
		           TTensorI(TPrm(L(2^(t-i+1), 2^(t-i))), 2^(i-1), APar, APar)
		       ])
	            )),
		    TTensorInd(SortConfigBase(access_f(t)), k, APar, APar)]
		    #TTensorI(SortBase(), 2^(t-1), APar, APar)] #what is this line?
                ).withTags(nt.getTags())
            ]]
	),

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

    ),

   Sort_Stream_Iter := rec(
        info         := "Stream/Iter sorting network",

	depth_params := [],

        applicable   := (self, nt) >> Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and
	                              Length(self.depth_params) = Log2Int(nt.params[1])-1 and
				      let(
					  t := Log2Int(nt.params[1]),
					  d := self.depth_params,
				      ForAll(List([1..t-1], i -> IsInt((t-i+1)/d[i])), j -> j)
				      ),

        children := (self, nt) >> let(
   	    t := Log2Int(nt.params[1]),

	    tag_w_tmp := nt.tags[1],
	    tag_w := tag_w_tmp.bs,
	    p := Ind(2^t),

	    get_bb := w -> Cond(w=1, TTensorInd(SortBase_w1(p), p, APar, APar),TTensorI(SortBase(), 2^(t-1), APar, APar)),

	    stage := i >> TCompose([
			     get_bb(tag_w),
			     #TTensorI(SortBase(), 2^(t-1), APar, APar),
			     TTensorI(TPrm(L(2^(t-i+1), 2^(t-i))) , 2^(i-1), APar, APar)
			  ]),

	    full_stage := i >> TCompose(List([1..self.depth_params[i]], j2 -> stage(i))),

  	    [[ TCompose(
	           [TCompose(List([1..t-1], i -> let(
		       j := Ind(t-i+1),
		       d := self.depth_params[i],
		       j1 := Ind(d),
		       j2 := Ind((t-i+1)/d),
	               TCompose([
			   Cond(d = ((t-i+1)),
                               full_stage(i),
				TCompose(List([1..d],j1 ->
				TICompose(j2, (t-i+1)/d, stage(i))
				))
			   ),
		       	   #TTensorI(TPrm(DirectSum(I(2^(t-i)), J(2^(t-i)))), 2^(i-1), APar, APar)
		       	   TTensorI(TPrm(SortIJPerm(2^(t-i+1))), 2^(i-1), APar, APar)
		       ])
	            ))),
		    get_bb(tag_w)] #last iterations left outside of TCompose
		    #TTensorI(SortBase(), 2^(t-1), APar, APar)] #last iterations left outside of TCompose
               ).withTags(nt.getTags())
            ]]
	),

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

   ),

   #old version -works-
#      Sort_Stream_Iter := rec(
#        info         := "Stream/Iter sorting network",
#
#	depth_params := [],
#
#        applicable   := (self, nt) >> Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and
#	                              Length(self.depth_params) = Log2Int(nt.params[1])-1 and
#				      let(
#					  t := Log2Int(nt.params[1]),
#					  d := self.depth_params,
#				      ForAll(List([1..t-1], i -> IsInt((t-i+1)/d[i])), j -> j)
#				      ),
#
#
#        children := (self, nt) >> let(
#   	    t := Log2Int(nt.params[1]),
#
#	    stage := i >> TCompose([
#			     TTensorI(SortBase(), 2^(t-1), APar, APar),
#			     TTensorI(TPrm(L(2^(t-i+1), 2^(t-i))) , 2^(i-1), APar, APar)
#			  ]),
#
#	    full_stage := i >> TCompose(List([1..self.depth_params[i]], j2 -> stage(i))),
#
#
#  	    [[ TCompose(
#	           [TCompose(List([1..t-1], i -> let(
#		       j := Ind(t-i+1),
#		       d := self.depth_params[i],
#		       j1 := Ind((t-i+1)/d),
#	               TCompose([
#			   Cond(j1.range = 1,
#			       full_stage(i),
#			       TICompose(j1, (t-i+1)/d, full_stage(i))
#			   ),
#		       	   TTensorI(TPrm(DirectSum(I(2^(t-i)), J(2^(t-i)))), 2^(i-1), APar, APar)
#		       ])
#	            ))),
#		    TTensorI(SortBase(), 2^(t-1), APar, APar)]
#               ).withTags(nt.getTags())
#            ]]
#	),
#
#        apply        := (nt, c, cnt) -> c[1],
#
#   ),

   Sort_Stream5 := rec(
        info         := "Version of Sort_Stream_Iter (sortAlg2) removing J permutations and with configurable 2-input sorters",

	depth_params := [],

        applicable   := (self, nt) >> Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and
	                              Length(self.depth_params) = Log2Int(nt.params[1])-1 and
				      let(
					  t := Log2Int(nt.params[1]),
					  d := self.depth_params,
				      ForAll(List([1..t-1], i -> IsInt((t-i+1)/d[i])), j -> j)
				      ),

        children := (self, nt) >> let(
   	    t := Log2Int(nt.params[1]),
	    k := Ind(2^(t-1)),
	    z := (s) >> t-s,

	    c1 := (s) >> logic_and(eq(bit_sel(k, z(s)), 1), neq(s, 1)),
	    access_f := (s) >> cond(c1(s), 1, 2),

	    stage := i >> TCompose([
			     TTensorInd(SortConfigBase(access_f(i)), k, APar, APar),
			     TTensorI(TPrm(L(2^(t-i+1), 2^(t-i))) , 2^(i-1), APar, APar)
			  ]),

	    full_stage := i >> TCompose(List([1..self.depth_params[i]], j2 -> stage(i))),

  	    [[ TCompose(
	           [TCompose(List([1..t-1], i -> let(
		       j := Ind(t-i+1),
		       d := self.depth_params[i],
		       j1 := Ind(d),
		       j2 := Ind((t-i+1)/d),
		       Cond(d = ((t-i+1)),
                                full_stage(i),
				TCompose(List([1..d],j1 ->
				TICompose(j2, (t-i+1)/d, stage(i))
				))
			       #unroll this change to TCompose
			)
	            ))),
		    TTensorInd(SortConfigBase(access_f(t)), k, APar, APar)] #last iterations left outside of TCompose
               ).withTags(nt.getTags())
            ]]
	),

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

   ),

  Sort_Stream3 := rec(
        info         := "",

	depth_out := 1,
        depth_in := [],

        applicable   := (self, nt) >> Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and
			     	      Length(self.depth_in) = Log2Int(nt.params[1])-1  and
				      let(
                                          t := Log2Int(nt.params[1]), d_out := self.depth_out,
                                          d_in := self.depth_in, IsInt(t/d_out) and ((d_out=1) or ((d_out=t) and
                                      	  (ForAll(List([1..t-1], i -> IsInt((t-i+1)/d_in[i])), j -> j))))
                                      ),

        children := (self, nt) >> let(
	    d_out := self.depth_out,
	    d_in := self.depth_in,
            t := Log2Int(nt.params[1]),
	    k := Ind(2^(t-1)),
	    iter_d1 := Ind(t),

            z := (lp, jp) >> (t-1)-(lp+jp),
            c2 := (lp, jp) >> logic_and(eq(bit_sel(k, z(lp, jp)), 1), neq(lp, 0)),
            access_f := (lp, jp) >> cond(c2(lp, jp), 1, 2),
            stage := (lp, jp) >> TCompose([
                       TTensorInd(SortConfigBase(access_f(lp, jp)), k, APar, APar),
                       TPrm(L(2^t, 2^(t-1)))
                   ]),
	    full_stage := i >> TCompose(List([0..d_in[i+1]-1], j -> stage(i,j))),
	    [[ Cond(d_out=1,
			TICompose(iter_d1,t,let(
				iter_d2 := Ind(t-iter_d1),
	    			p_list := List([0..t-1], i-> get_l_power(t,i)),
				TCompose([
					TICompose(iter_d2,t-iter_d1, stage(iter_d1,iter_d2)),
		  	 		TPrmMulti(p_list,iter_d1)
				])
			)).withTags(nt.getTags()),
		    d_out=t,
			TCompose([
				TCompose(List([0..t-2], iter_d3 -> let(
				  d_stage := d_in[iter_d3+1],
            			  iter_d5 := Ind((t-iter_d3)/d_stage),
				  TCompose([
					Cond(d_stage=(t-iter_d3),
						full_stage(iter_d3),
						TCompose(List([0..d_stage-1],iter_d4 ->
						  TICompose(iter_d5,(t-iter_d3)/d_stage,stage(iter_d3,iter_d4*iter_d5))))
					),
					get_l_power(t,iter_d3)
				  ])
				))),
				stage(t-1,0),
				get_l_power(t,t-1)
			]).withTags(nt.getTags())
	    )]]

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

   Sort_Stream4 := rec(
        info         := "",

	depth := 1,

        applicable   := (self, nt) >> Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and
	                              let (d := self.depth, t := Log2Int(nt.params[1]), IsInt(t*t/self.depth) and
				      (IsInt(d/t) or IsInt(t/d))),

        children := (self, nt) >> let(
   	    t := Log2Int(nt.params[1]),
	    d := self.depth,
	    d1 := cond(leq(d,t), d, t).ev(),
	    d2 := cond(leq(d,t), 1, d/t).ev(),

	    k := Ind(2^(t-1)),
	    j := Ind(t),
	    l := Ind(t),
	    n := Ind(t/d2),
	    v := Ind(t/d1),

            d_tmp := cond(leq(d,t), t/d, 0).ev(),
            d2_tmp := cond(leq(t,d), d/t, 0).ev(),
	    s_tmp := ((t*t)/d),
            m2 := Ind(d_tmp),
	    s2 := Ind(s_tmp),
	    v2 := Ind(d2_tmp),
	    n2 := Ind(d),
	    n3 := Ind(d),
	    l2 := Ind(t),

	    tag_w_tmp := nt.tags[1],
	    tag_w := tag_w_tmp.bs,
	    p := Ind(2^t),

	    c1 := (lp, jp) >> lt((t-1), (lp+jp)),
	    z := (lp, jp) >> (t-1)-(lp+jp),
	    z_w1 := (lp, jp) >> (t-1)-(lp+jp)+1,
	    c2 := (lp, jp) >> logic_and(eq(bit_sel(k, z(lp, jp)), 1), neq(lp, 0)),
	    c2_w1 := (lp, jp) >> logic_and(eq(bit_sel(p, z_w1(lp, jp)), 1), neq(lp, 0)),

	    access_f := (lp, jp) >> cond(c1(lp, jp), 0, c2(lp, jp), 1, 2),
	    access_f_w1 := (lp, jp) >> cond(c1(lp, jp), 0, c2_w1(lp, jp), 1, 2),

	    get_bb := (lp,jp) -> Cond(tag_w=1, TTensorInd(SortConfigBase_w1(p,access_f_w1(lp,jp)), p, APar, APar),TTensorInd(SortConfigBase(access_f(lp, jp)), k, APar, APar)),

	    stage := (lp, jp) >> TCompose([
		       get_bb(lp, jp),
		       #TTensorInd(SortConfigBase(access_f(lp, jp)), k, APar, APar),
		       TPrm(L(2^t, 2^(t-1)))
	           ]),

	    full_stage := np_1 >> TCompose(List([0..t-1], m_1 -> TCompose(List([0..t-1], j_1 -> stage(m_1, j_1))))),
	    full_stage1 := np >> TCompose(List([0..d2-1], m -> TCompose(List([0..t-1], j -> stage(d2*np+m, j))))),
	    full_stage2 := vp >> TCompose(List([0..d1-1], s -> stage(l, vp+s))),
            full_stage1b := np2 >> TICompose(m2,d_tmp, TICompose(j,t, stage((t/d)*np2+m2, j))),

             # Old: problem is that it's assuming the l2 above, which is an unassigned iterator.
	     # There is also a problem with the vp2+s2 parameter: you need to multiply vp2 by the number of iterations.
             # full_stage2b := (vp2) >> TICompose(s2,s_tmp, stage(l2, vp2+s2)),
            full_stage2b := (vp2, l3) >> TICompose(s2,s_tmp, stage(l3, vp2*s_tmp+s2)),

  	    [[ Cond(d=t*t, full_stage(0).withTags(nt.getTags()),
		    d<t, TCompose(List([0..d-1], n2 -> full_stage1b(n2))).withTags(nt.getTags()),
                    d=t, TCompose(List([0..d-1], n3 -> full_stage1b(n3))).withTags(nt.getTags()),
                    d>t, TCompose(List([0..t-1], l3 -> TCompose(List([0..d2_tmp-1], v2 -> full_stage2b(v2, l3))))).withTags(nt.getTags())) #the problem seems to be the outer most TCompose works if it was TICompose
		    #d>t, TICompose(n, t/d2, full_stage1(n)).withTags(nt.getTags())) #old one but will leave it as new one does not work yet
	        ]]
	),

#	Old version -works-
#	   Sort_Stream4 := rec(
#        info         := "",
#
#	depth := 1,
#
#        applicable   := (self, nt) >> Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and
#	                              let (d := self.depth, t := Log2Int(nt.params[1]), IsInt(t*t/self.depth) and
#				      (IsInt(d/t) or IsInt(t/d))),
#
#
#        children := (self, nt) >> let(
#   	    t := Log2Int(nt.params[1]),
#	    d := self.depth,
#	    d1 := cond(leq(d,t), d, t).ev(),
#	    d2 := cond(leq(d,t), 1, d/t).ev(),
#
#	    k := Ind(2^(t-1)),
#	    j := Ind(t),
#	    l := Ind(t),
#	    n := Ind(t/d2),
#	    v := Ind(t/d1),
#
#
#	    c1 := (lp, jp) >> lt((t-1), (lp+jp)),
#	    z := (lp, jp) >> (t-1)-(lp+jp),
#	    c2 := (lp, jp) >> logic_and(eq(bit_sel(k, z(lp, jp)), 1), neq(lp, 0)),
#
#	    access_f := (lp, jp) >> cond(c1(lp, jp), 0, c2(lp, jp), 1, 2),
#
#	    stage := (lp, jp) >> TCompose([
#		       TTensorInd(SortConfigBase(access_f(lp, jp)), k, APar, APar),
#		       TPrm(L(2^t, 2^(t-1)))
#	           ]),
#
#	    full_stage1 := np >> TCompose(List([0..d2-1], m -> TCompose(List([0..t-1], j -> stage(d2*np+m, j))))),
#	    full_stage2 := vp >> TCompose(List([0..d1-1], s -> stage(l, vp+s))),
#
#  	    [[ Cond(d=t*t, full_stage1(0).withTags(nt.getTags()),
#		    d>t, TICompose(n, t/d2, full_stage1(n)).withTags(nt.getTags()),
#		    d<t, TICompose(l, t, TICompose(v, t/d1, full_stage2(d1*v))).withTags(nt.getTags()),
#		    d=t, TICompose(l, t, full_stage2(0)).withTags(nt.getTags()))
#	        ]]
#	),
#
#        apply        := (nt, c, cnt) -> c[1],
#   ),

	# this works, but only for d=1.
#         children := (self, nt) >> let(
#    	    t := Log2Int(nt.params[1]),
# 	    k := Ind(2^(t-1)),
# 	    l := Ind(t),
# 	    j := Ind(t),

# 	    c1 := lt((t-1), (l+j)),
# 	    z := (t-1)-(l+j),
# 	    c2 := logic_and(eq(bit_sel(k, z), 1), neq(l, 0)),

# 	    access_f := cond(c1, 0, c2, 1, 2),

# 	    d1 := cond(leq(d,t), d, t),
# 	    d2 := cond(leq(d,t), 1, d/t),

# 	    c1 := lt((t-1), (l+j)),
# 	    z := (t-1)-(l+j),
# 	    c2 := logic_and(eq(bit_sel(k, z), 1), neq(l, 0)),

# 	    access_f := cond(c1, 0, c2, 1, 2),

# 	    d1 := cond(leq(d,t), d, t),
# 	    d2 := cond(leq(d,t), 1, d/t),

#   	    [[ TICompose(l, t,
# 		   TICompose(j, t, TCompose([
# 		       TTensorInd(SortConfigBase(access_f), k, APar, APar),
# 		       TPrm(TL(2^t, 2^(t-1), 1, 1))
# 	           ]))).withTags(nt.getTags())
#             ]]
# 	),

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

   ),

  Linear_Sort := rec(
        info         := "",

        applicable   := (self, nt) >> IsTwoPower(nt.params[1]),

        children := (self, nt) >> let(
            t := Log2Int(nt.params[1]),
	    [[ TCompose(List([1..2^t], i ->
		TTensorI(LinearSortBase(), nt.params[1], APar, APar)
			)).withTags(nt.getTags()),
	    ]]

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

));

#---------------------------------------------------------------------
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# Old stuff, not used now

#     Sort_Stream_old := rec(
#         info         := "Streaming sorting network",

#         switch       := true,
#         applicable   := nt ->
#             Length(nt.params) = 1 and IsTwoPower(nt.params[1]),

#         children := (self, nt) >> let(
# 	    t := Log2Int(nt.params[1]),
# 	    [[ TCompose(List([1..t], i ->
# 	 	TCompose([
# 		    TTensorI(BitonicSort(2^(t-i+1)), 2^(i-1), APar, APar),
# 		    TTensorI(TPrm(DirectSum(I(2^(t-i)), J(2^(t-i)))), 2^(i-1), APar, APar)
# 		])
#             )).withTags(nt.getTags())]]
#         ),

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

#     ),

# Declaration of a bitonic sorter of size n
# Class(BitonicSort, TaggedNonTerminal, rec(
#     abbrevs := [
#     (n)       -> Checked(IsPosIntSym(n), [_unwrap(n)]),
#     ],

#     hashAs := self >> ObjId(self)(self.params[1]).withTags(self.getTags()),

#     dims := self >> [ self.params[1], self.params[1] ],

#     terminate := self >> Error("not supported"), # we could probably support this
# ));

# Old stuff, not used now.
# # Rules to break-down BitonicSort()
# NewRulesFor(BitonicSort, rec(
#     BitonicSort_Stream := rec(
#         info         := "Streaming Bitonic sorting network",

#         switch       := true,
#         applicable   := nt ->
#             Length(nt.params) = 1 and IsTwoPower(nt.params[1]) and nt.params[1] > 2,

#         children := (self, nt) >> let(
# 	    k := Log2Int(nt.params[1]),
# 	    [[ TCompose([ TTensorI(SortBase(), 2^(k-1), APar, APar),
# 		  TCompose(List([2..k], j -> TCompose([
# 		      TTensorI(TPrm(Tensor(I(2), L(2^(j-1), 2^(j-2))) * L(2^j, 2)), 2^(k-j), APar, APar),
# 		      TTensorI(SortBase(), 2^(k-1), APar, APar)
# 		  ]))),
# 		  TPrm(L(2^k, 2^(k-1)))
# 	       ]).withTags(nt.getTags())
# 	    ]]
#         ),

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

#     BitonicSort_Base := rec(
# 	info := "Bitonic soring network base rule",
# 	applicable := nt -> nt.params[1] = 2,
# 	chldren := (self, nt) >> [[ ]],
# 	apply := (nt, c, cnt) -> SortBase()
#     )

# ));


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

X := var("X", TPtr(TReal));
Y := var("Y", TPtr(TReal));
X1 := var("X1", TPtr(TReal));
Y1 := var("Y1", TPtr(TReal));
XY1 := var("XY1", TPtr(TReal));

# defined in cgstrat.gi
Declare(ApplyCodegenStrat);

RemoveAssignAcc := code -> SubstTopDownRulesNR(code, rec(
    assign_acc_toassign := Rule(assign_acc, e -> assign(Copy(e.loc), e.loc+e.exp)),
    assign_donothing := Rule(assign, e -> e),
));

#F CodeSums(<sums>, <opts>)
#F    Creates code for Sigma-SPL <sums>.
#F    Sigma-SPL rewrite rules are not applied.
#F    Compilation proceeds according to <opts>.compileStrategy
#F

CodeSums := function(sums, opts)
    local X, Y, code;

        if IsList(sums.dims()[2]) then
            X := List([1..Length(sums.dims()[2])],
                x -> var(Concatenation("X",String(x)), TPtr(TReal)));
        else
            X := Cond(IsBound(opts.X), opts.X, var("X", TPtr(TReal)));
        fi;

        if IsList(sums.dims()[1]) then
            Y := List([1..Length(sums.dims()[1])],
                x -> var(Concatenation("Y",String(x)), TPtr(TReal)));
        else
            Y := Cond(
                opts.inplace, X,
                IsBound(opts.Y), opts.Y,
                var("Y", TPtr(TReal)));
        fi;

        code := opts.codegen(Formula(sums), Y, X, opts);
        code.ruletree := Cond(IsBound(sums.ruletree), sums.ruletree, rec());
        return code;
end;

#F See CodeSums
#F
CodeSumsOpts := CodeSums;

#F CodeRuleTree(<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
CodeRuleTree := function(rt, opts)
    local sums, code;
    sums := SumsRuleTree(rt, opts);
    code := CodeSums(sums, opts);
    return code;
end;

#F See CodeRuleTree
#F
CodeRuleTreeOpts := CodeRuleTree;

#F RealSums(<sums>)
#F    Convert complex Sigma-SPL formula to a real formula.
#F
RealSums := sums -> StandardSumsRules(RC(sums));

#F CodeSPL(<unroll>, <spl>, opts)
#F
CodeSPL := function(spl, opts)
    local sums;
    sums := SumsSPL(spl, opts);
    sums := ApplyStrategy(sums, opts.formulaStrategies.sigmaSpl, UntilDone, opts);
    sums := ApplyStrategy(sums, opts.formulaStrategies.preRC, UntilDone, opts);
    if not spl.isReal() and not opts.generateComplexCode then
        sums := ApplyStrategy(RC(sums), opts.formulaStrategies.rc, UntilDone, opts); fi;
    sums := ApplyStrategy(sums, opts.formulaStrategies.postProcess, UntilDone, opts);
    return CodeSums(sums, opts);
end;

#F PrintCode(<funcname>, <code>, <opts>)
#F    Prints the code using opts.unparser
#F
PrintCode := (funcname, code, opts) -> opts.unparser.gen(funcname, code, opts);

_ExportCodeRuleTree := function(ruletree, file, funcname, opts)
    local code;
    Constraint(IsRuleTree(ruletree));
    if opts.verbosity > 0 then Print("Generating code...\n"); fi;
    code := CodeRuleTree(ruletree, opts);
    PrintTo(file, opts.unparser.gen(funcname, code, opts));
end;

#F ExportCodeRuleTree(<ruletree>, <funcname>, <opts>)
#F    Generates and exports C code to a file for <ruletree>.
#F    The C transform function will have the name <funcname>.
#F    Code will be saved in <funcname>.c
#F
ExportCodeRuleTree := (ruletree, funcname, opts) ->
    _ExportCodeRuleTree(ruletree, Concat(funcname, ".c"), funcname, opts);

#F PrintCodeRuleTree(<ruletree>, <opts>)
#F    Generates C code and prints it out.
#F
PrintCodeRuleTree := (ruletree, opts) ->
    _ExportCodeRuleTree(ruletree, "*stdout*", "sub", opts);

#F ImplementRuleTree(<ruletree>, <file>, <opts>)
#F
ImplementRuleTree := (ruletree, file, opts) ->
    _ExportCodeRuleTree(ruletree, file,
    When(IsBound(opts.subName), opts.subName, ruletree.node.name), opts);

#F VerifyMatrixRuleTree(<ruletree>, <opts>)
#F
VerifyMatrixRuleTree := function(ruletree, opts)
    local code, mat;
    Constraint(IsRuleTree(ruletree));
    if opts.verbosity > 0 then Print("Generating code...\n"); fi;
    code := CodeRuleTree(ruletree, opts);
    if opts.verbosity > 0 then Print("Computing reference matrix...\n"); fi;
    mat := When(ruletree.node.isReal() or opts.dataType = "complex" or opts.generateComplexCode,
            MatSPL(ruletree.node),
        RCMatCyc(MatSPL(ruletree.node)));
    if opts.verbosity > 0 then Print("Running code and computing the norm...\n"); fi;
    return InfinityNormMat(CMatrix(code, opts) - mat);
end;

#F VerifyMatrixCode(<code>, <definition-matrix>, <opts>)
#F
VerifyMatrixCode := (code, def_matrix, opts) -> Checked(IsCommand(code),
    InfinityNormMat(CMatrix(code, opts) - def_matrix)
);

FailedTrees := [];
InaccurateTrees := [];

CMeasureRuleTree := function(rt, opts)
    local res, c, mfunc, tol;

    c := CodeRuleTree(rt, opts);

    mfunc := When(IsBound(opts.profile) and IsBound(opts.profile.meas), opts.profile.meas, CMeasure);

    if opts.faultTolerant then
		res := Try(mfunc(c, opts));
    else
		res := [true, mfunc(c, opts)];
    fi;

    if res[1]=false then
        Add(FailedTrees, rt);
        return 1e20;
    else
        if IsBound(opts.verifyDP) and opts.verifyDP then
            tol := VerifyMatrixRuleTree(rt, opts);
            if tol > opts.verifyTolerance then
                Add(InaccurateTrees, rec(rt:=rt, opts := opts, tol :=tol));
            fi;
        fi;
        return res[2];
    fi;
end;


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

# get “system default” as defined by SPIRAL startup scripts
LocalConfig.defaultConf();

# pick from a number of default configs
LocalConfig.supportedConfs.confGPU();
LocalConfig.supportedConfs.confMultiGPU();
LocalConfig.supportedConfs.confSclarCPU();
LocalConfig.supportedConfs.confOMPVMX();
…

# guru interface by configuring confs
LocalConfig.defaultConf(rec(useCPU := true, useGPU := false, useP9 := true, useOpenMP := true));
LocalConfig.defaultConf(rec(useCPU := false, useGPU := true, useMultiGPU := false));
LocalConfig.supportedConfs.confOMPVMX(rec(threads := 4, OMPver := “4.1.2”));


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

###############################################################################
##
#M  AutomatonSemigroup(<list>)
##
InstallMethod(AutomatonSemigroup, "for [IsList]", [IsList],
function (list)
  return AutomatonSemigroup(list, false);
end);

###############################################################################
##
#M  AutomatonSemigroup(<list>, <bind_vars>)
##
InstallMethod(AutomatonSemigroup, "for [IsList, IsBool]", [IsList, IsBool],
function (list, bind_vars)
  if not AG_IsCorrectAutomatonList(list, false) then
    Error("in AutomatonSemigroup(IsList):\n",
          "  given list is not a correct list representing automaton\n");
  fi;

  # XXX
  return SemigroupOfAutomFamily(AutomFamily(list, bind_vars));
end);

###############################################################################
##
#M  AutomatonSemigroup(<list>, <names>)
##
InstallMethod(AutomatonSemigroup, "for [IsList, IsList]", [IsList, IsList],
function (list, names)
  if not AG_IsCorrectAutomatonList(list, false) then
    Error("error in AutomatonSemigroup(IsList, IsList):\n",
          "  given list is not a correct list representing automaton\n");
  fi;

  # XXX
  return SemigroupOfAutomFamily(AutomFamily(list, names));
end);

###############################################################################
##
#M  AutomatonSemigroup(<list>, <names>, <bind_vars>)
##
InstallMethod(AutomatonSemigroup, "for [IsList, IsList, IsBool]",
              [IsList, IsList, IsBool],
function (list, names, bind_vars)
  if not AG_IsCorrectAutomatonList(list, false) then
    Error("error in AutomatonSemigroup(IsList):\n",
          "  given list is not a correct list representing automaton\n");
  fi;

  #XXX
  return SemigroupOfAutomFamily(AutomFamily(list, names, bind_vars));
end);

###############################################################################
##
#M  AutomatonSemigroup(<string>)
#M  AutomatonSemigroup(<string>, <bind_vars>)
##
InstallMethod(AutomatonSemigroup, "for [IsString]", [IsString],
function(string)
    return AutomatonSemigroup(string, AG_Globals.bind_vars_autom_family);
end);
InstallMethod(AutomatonSemigroup, "AutomatonSemigroup(IsString, IsBool]", [IsString, IsBool],
function(string, bind_vars)
    local s;
    s := AG_ParseAutomatonString(string);
    return AutomatonSemigroup(s[2], s[1], bind_vars);
end);

###############################################################################
##
#M  AutomatonSemigroup(<A>)
#M  AutomatonSemigroup(<A>, <bind_vars>)
##
InstallMethod(AutomatonSemigroup, "for [IsMealyAutomaton]", [IsMealyAutomaton],
function(A)
  return AutomatonSemigroup(AutomatonList(A), A!.states);
end);

InstallMethod(AutomatonSemigroup, "for [IsMealyAutomaton, IsBool]", [IsMealyAutomaton, IsBool],
function(A, bind_vars)
  return AutomatonSemigroup(AutomatonList(A), A!.states, bind_vars);
end);

###############################################################################
##
#M  IsSelfSimilar(<G>)
##
InstallMethod(IsSelfSimilar, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  local g, i, res;
  res := true;
  for g in GeneratorsOfSemigroup(G) do
    for i in [1..UnderlyingAutomFamily(G)!.deg] do
      res := Section(g, i) in G;
      if res = fail then
        TryNextMethod();
      elif not res then
        return false;
      fi;
    od;
  od;
  return true;
end);

###############################################################################
##
#M  UnderlyingAutomFamily(<G>)
##
InstallMethod(UnderlyingAutomFamily, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return FamilyObj(GeneratorsOfSemigroup(G)[1]);
end);

# ###############################################################################
# ##
# #M  UseSubsetRelation(<G>)
# ##
# InstallMethod(UseSubsetRelation,
#               "for [IsAutomSemigroup, IsAutomSemigroup]",
#               [IsAutomSemigroup, IsAutomSemigroup],
# function(super, sub)
#   ## the full group is self similar, so if <super> is smaller than the full
#   ##  group then sub is smaller either
#   if HasIsGroupOfAutomFamily(super) then
#     if not IsGroupOfAutomFamily(super) then
#       SetIsGroupOfAutomFamily(sub, false); fi; fi;
#   TryNextMethod();
# end);

# ###############################################################################
# ##
# #M  __AG_SubgroupOnLevel(<G>, <gens>, <level>)
# ##
# InstallMethod(__AG_SubgroupOnLevel, [IsAutomGroup,
#                                  IsList and IsTreeAutomorphismCollection,
#                                  IsPosInt],
# function(G, gens, level)
#   local overgroup;
#
#   if IsEmpty(gens) or (Length(gens) = 1 and IsOne(gens[1])) then
#     return TrivialSubgroup(G);
#   fi;
#
#   if HasIsGroupOfAutomFamily(G) and IsGroupOfAutomFamily(G) then
#     overgroup := G;
#   else
#     overgroup := GroupOfAutomFamily(UnderlyingAutomFamily(G));
#   fi;
#
#   return SubgroupNC(overgroup, gens);
# end);
#
# InstallOtherMethod(__AG_SubgroupOnLevel, [IsAutomGroup, IsList and IsEmpty, IsPosInt],
# function(G, gens, level)
#   return TrivialSubgroup(G);
# end);
#
# InstallMethod(__AG_SubgroupOnLevel, [IsTreeAutomorphismGroup,
#                                  IsList and IsAutomCollection,
#                                  IsPosInt],
# function(G, gens, level)
#   local overgroup;
#
#   overgroup := GroupOfAutomFamily(FamilyObj(gens[1]));
#
#   if Length(gens) = 1 and IsOne(gens[1]) then
#     return TrivialSubgroup(overgroup);
#   fi;
#
#   return SubgroupNC(overgroup, gens);
# end);
#
# InstallMethod(__AG_SimplifyGroupGenerators, [IsList and IsAutomCollection],
# function(gens)
#   local words, fam;
#
#   if IsEmpty(gens) then
#     return [];
#   fi;
#
#   fam := FamilyObj(gens[1]);
#   words := FreeGeneratorsOfGroup(Group(List(gens, a -> a!.word)));
#
#   if fam!.use_rws and not IsEmpty(words) then
#     words := AG_ReducedForm(fam!.rws, words);
#     words := FreeGeneratorsOfGroup(Group(words));
#   fi;
#
#   return List(words, w -> Autom(w, fam));
# end);

###############################################################################
##
#M  DegreeOfTree(<G>)
##
InstallMethod(DegreeOfTree, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return DegreeOfTree(UnderlyingAutomFamily(G));
end);

InstallMethod(TopDegreeOfTree, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return DegreeOfTree(UnderlyingAutomFamily(G));
end);

###############################################################################
##
#M  PrintObj(<G>)
##
InstallMethod(PrintObj, "for [IsAutomatonSemigroup]",
              [IsAutomatonSemigroup],
function(G)
  Print("AutomatonSemigroup(\"", String(G), "\")");
end);

#############################################################################
##
#M  String(<G>)
##
InstallMethod(String, "for [IsAutomSemigroup]", [IsAutomSemigroup],
function(G)
  local i, gens, formatone, s;

  formatone := function(a)
    return Concatenation(String(a), " = ", String(Decompose(a)));
  end;

  if IsMonoid(G) then
    gens := GeneratorsOfMonoid(G);
  else
    gens := GeneratorsOfSemigroup(G);
  fi;

  s := "";
  for i in [1..Length(gens)] do
    Append(s, formatone(gens[i]));
    if i <> Length(gens) then
      Append(s, ", ");
    fi;
  od;

  return s;
end);

###############################################################################
##
#M  Display(<G>)
##
InstallMethod(Display, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  local i, gens, printone;

  printone := function(a)
    Print(a, " = ", Decompose(a));
  end;

  gens := GeneratorsOfSemigroup(G);
  if gens = [] then Print("< >"); fi;
  if Length(gens) = 1 then
    Print("< "); printone(gens[1]); Print(" >");
  else
    Print("< "); printone(gens[1]); Print(", \n");
    for i in [2..Length(gens)-1] do
      Print("  "); printone(gens[i]); Print(", \n");
    od;
    Print("  "); printone(gens[Length(gens)]); Print(" >");
  fi;
end);

###############################################################################
##
#M  ViewObj(<G>)
##
InstallMethod(ViewObj, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  local i, gens;
  gens := List(GeneratorsOfSemigroup(G), g -> Word(g));
  if gens = [] then Print("< >"); fi;
  Print("< ");
  for i in [1..Length(gens)-1] do
    if IsOne(gens[i]) then
      Print(AG_Globals.identity_symbol, ", ");
    else
      Print(gens[i], ", ");
    fi;
  od;
  if IsOne(gens[Length(gens)]) then
    Print(AG_Globals.identity_symbol, " >");
  else
    Print(gens[Length(gens)], " >");
  fi;
end);

###############################################################################
##
#M  IsTrivial(G)
##
InstallMethod(IsTrivial, "for [IsAutomSemigroup]", [IsAutomSemigroup],
function (G)
  local g;
  for g in GeneratorsOfSemigroup(G) do
    if not IsOne(g) then return false; fi;
  od;
  return true;
end);

###############################################################################
##
#M  Size(G)
##
InstallMethod(Size, "for [IsAutomSemigroup]", [IsAutomSemigroup],
function (G)
  local g;
  if IsTrivial(G) then
    Info(InfoAutomGrp, 3, "Size(G): 1, G is trivial");
    return 1;
  fi;

  for g in Iterator(G) do od;

  return Size(G);
#  TryNextMethod();
end);

# ###############################################################################
# ##
# #M  \= (<G>, <H>)
# ##
# InstallMethod(\=, "for [IsAutomGroup, IsAutomGroup]",
#               IsIdenticalObj, [IsAutomGroup, IsAutomGroup],
# function(G, H)
#   local fgens1, fgens2, fam;
#
#   if HasIsGroupOfAutomFamily(G) and HasIsGroupOfAutomFamily(H) then
#     if IsGroupOfAutomFamily(G) <> IsGroupOfAutomFamily(H) then
#       Info(InfoAutomGrp, 3, "G = H: false, exactly one is GroupOfAutomFamily");
#       return false;
#     fi;
#     if IsGroupOfAutomFamily(G) then
#       Info(InfoAutomGrp, 3, "G = H: true, both are GroupOfAutomFamily");
#       return true;
#     fi;
#   fi;
#
#   fgens1 := List(GeneratorsOfGroup(G), g -> Word(g));
#   fgens2 := List(GeneratorsOfGroup(H), g -> Word(g));
#   fam := UnderlyingAutomFamily(G);
#
#   if fam!.rws <> fail then
#     fgens1 := AsSet(AG_ReducedForm(fam!.rws, fgens1));
#     fgens2 := AsSet(AG_ReducedForm(fam!.rws, fgens2));
#   fi;
#
#   if GroupWithGenerators(fgens1) = GroupWithGenerators(fgens2) then
#     Info(InfoAutomGrp, 3, "G = H: true, by subgroups of free group");
#     return true;
#   fi;
#
#   TryNextMethod();
# end);

# ###############################################################################
# ##
# #M  IsSubset (<G>, <H>)
# ##
# InstallMethod(IsSubset, "for [IsAutomGroup, IsAutomGroup]",
#               IsIdenticalObj, [IsAutomGroup, IsAutomGroup],
# function(G, H)
#   local h, fam, fgens1, fgens2;
#
#   if HasIsGroupOfAutomFamily(G) and IsGroupOfAutomFamily(G) then
#     Info(InfoAutomGrp, 3, "IsSubgroup(G, H): true");
#     Info(InfoAutomGrp, 3, "  G is GroupOfAutomFamily");
#     return true;
#   fi;
#
#   fgens1 := List(GeneratorsOfGroup(G), g -> Word(g));
#   fgens2 := List(GeneratorsOfGroup(H), g -> Word(g));
#   fam := UnderlyingAutomFamily(G);
#
#   if fam!.rws <> fail then
#     fgens1 := AsSet(AG_ReducedForm(fam!.rws, fgens1));
#     fgens2 := AsSet(AG_ReducedForm(fam!.rws, fgens2));
#   fi;
#
#   if IsSubgroup(GroupWithGenerators(fgens1), GroupWithGenerators(fgens2)) then
#     Info(InfoAutomGrp, 3, "IsSubgroup(G, H): true");
#     Info(InfoAutomGrp, 3, "  by subgroups of free group");
#     return true;
#   fi;
#
#   TryNextMethod();
# end);

###############################################################################
##
#M  <g> in <G>
##
InstallMethod(\in, "for [IsAutom, IsAutomGroup]",
              [IsAutom, IsAutomSemigroup],
function(g, G)
  local fam, fgens, w;

  if HasIsGroupOfAutomFamily(G) and IsGroupOfAutomFamily(G) then
    return true;
  fi;

  fgens := List(GeneratorsOfSemigroup(G), g -> Word(g));
  w := Word(g);

  fam := UnderlyingAutomFamily(G);

  if fam!.rws <> fail then
    fgens := AsSet(AG_ReducedForm(fam!.rws, fgens));
    w := AG_ReducedForm(fam!.rws, w);
  fi;

  if w in SemigroupByGenerators(fgens) then
    Info(InfoAutomGrp, 3, "g in G: true");
    Info(InfoAutomGrp, 3, "  by elements of free group");
    Info(InfoAutomGrp, 3, "  g = ", g, "; G = ", G);
    return true;
  fi;

  TryNextMethod();
end);

###############################################################################
##
#M  Random(<G>)
##
InstallMethodWithRandomSource(Random, "for a random source and [IsAutomSemigroup]",
              [IsRandomSource, IsAutomSemigroup],
function(rs, G)
  local w, monoid, F, gens, pi;

  if IsAutomatonSemigroup(G) then
    monoid := UnderlyingFreeMonoid(G);

    if IsTrivial(monoid) then
      w := One(monoid);
    else
      while true do
        w := Random(rs, monoid);
        if not IsOne(w) then
          break;
        fi;
      od;
    fi;
    return Autom(w, UnderlyingAutomFamily(G));
  else
    gens := GeneratorsOfSemigroup(G);
    F := FreeGroup(Length(gens));
    pi := GroupHomomorphismByImagesNC(F,                      UnderlyingFreeGroup(G),
                                      GeneratorsOfGroup(F),   List(gens, Word)        );
    return Autom( Random( rs, SemigroupByGenerators( GeneratorsOfGroup(F)))^pi, UnderlyingAutomFamily(G));
  fi;
end);

###############################################################################
##
#M  UnderlyingFreeMonoid( <G> )
##
InstallMethod(UnderlyingFreeMonoid, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return UnderlyingFreeMonoid(UnderlyingAutomFamily(G));
end);

###############################################################################
##
#M  UnderlyingFreeGroup( <G> )
##
InstallMethod(UnderlyingFreeGroup, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return UnderlyingFreeGroup(UnderlyingAutomFamily(G));
end);

###############################################################################
##
#M  UnderlyingFreeGenerators( <G> )
##
InstallMethod(UnderlyingFreeGenerators, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return List(GeneratorsOfSemigroup(G), g -> Word(g));
end);

# ###############################################################################
# ##
# #M  UnderlyingFreeSubgroup(<G>)
# ##
# InstallMethod(UnderlyingFreeSubgroup, "for [IsAutomGroup]",
#               [IsAutomGroup],
# function(G)
#   local f;
#   if HasIsGroupOfAutomFamily(G) and IsGroupOfAutomFamily(G) then
#     return UnderlyingFreeGroup(G);
#   fi;
#   f := Subgroup(UnderlyingFreeGroup(G), UnderlyingFreeGenerators(G));
#   if f = UnderlyingFreeGroup(G) then
#     SetIsGroupOfAutomFamily(G, true);
#   fi;
#   return f;
# end);

###############################################################################
##
#M  UnderlyingFreeGroup( <G> )
##
InstallMethod(UnderlyingFreeGroup, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return UnderlyingAutomFamily(G)!.freegroup;
end);

###############################################################################
##
#M  UnderlyingFreeGenerators( <G> )
##
InstallMethod(UnderlyingFreeGenerators, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  return List(GeneratorsOfSemigroup(G), g -> Word(g));
end);

InstallMethod(SphericalIndex, "for IsAutomSemigroup",
              [IsAutomSemigroup],
function(G)
  return SphericalIndex(GeneratorsOfSemigroup(G)[1]);
end);
InstallMethod(DegreeOfTree, "for IsAutomSemigroup",
              [IsAutomSemigroup],
function(G)
  return UnderlyingAutomFamily(G)!.deg;
end);
InstallMethod(TopDegreeOfTree, "for IsAutomSemigroup",
              [IsAutomSemigroup],
function(G)
  return UnderlyingAutomFamily(G)!.deg;
end);

###############################################################################
##
#M  UnderlyingAutomaton(<G>)
##
InstallMethod(UnderlyingAutomaton, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  local fam, numstates, automatonlist, d, i, j;
  fam := UnderlyingAutomFamily(G);
  numstates := fam!.numstates;
# if you do the next 2 operations in one "List", it will remove unbinded spaces
  automatonlist := List(fam!.automatonlist);
  Apply(automatonlist, x -> ShallowCopy(x));
  d := fam!.deg;

# in case we have 1 in the list we move it to the numstates+1 postion
  if Length(automatonlist)=2*numstates+1 then
    for i in [1..numstates] do
      for j in [1..d] do
        if automatonlist[i][j]=2*numstates+1 then automatonlist[i][j] := numstates+1; fi;
      od;
    od;
    automatonlist[numstates+1] := List([1..d], x -> numstates+1);
    Add(automatonlist[numstates+1], automatonlist[2*numstates+1][d+1]);
    numstates := numstates+1;
  fi;
  return MealyAutomaton(automatonlist{[1..numstates]});
end);

###############################################################################
##
#M  IsAutomatonSemigroup(<G>)
##
InstallMethod(IsAutomatonSemigroup, "for [IsAutomSemigroup]",
              [IsAutomSemigroup],
function(G)
  if not HasIsAutomatonSemigroup(G) then return false; fi;
end);

###############################################################################
##
#M  SemigroupOfAutomFamily(<G>)
##
InstallMethod(SemigroupOfAutomFamily, "for [IsAutomSemigroup]",
                   [IsAutomSemigroup],
function(G)
  return SemigroupOfAutomFamily(UnderlyingAutomFamily(G));
end);

#E


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

#---------------------------------------------------------------------------------------
#   SPU vector instructions
#---------------------------------------------------------------------------------------

Class(vbinop_8x16i_spu, vbinop_spu, rec( v := self >> 8));
Class(vbinop_4x32f_spu, vbinop_spu, rec( v := self >> 4));
Class(vbinop_2x64f_spu, vbinop_spu, rec( v := self >> 2));

Class(exp_8x16i, rec(v := 8, computeType := self >> TVect(TInt,    8)));
Class(exp_4x32f, rec(v := 4, computeType := self >> TVect(TDouble, 4)));
Class(exp_2x64f, rec(v := 2, computeType := self >> TVect(TDouble, 2)));

Class(cmd_8x16i, rec(v := 8, computeType := self >> TVect(TInt,    8)));
Class(cmd_4x32f, rec(v := 4, computeType := self >> TVect(TDouble, 4)));
Class(cmd_2x64f, rec(v := 2, computeType := self >> TVect(TDouble, 2)));

# Load -----------------------------
Class(vloadu8_spu8x16i, vloadop_new, exp_8x16i, rec(numargs := 1));
Class(vloadu4_spu4x32f, vloadop_new, exp_4x32f, rec(numargs := 1));

# Store ----------------------------

# Zero -----------------------------
Class(vzero_8x16i, vop_new, exp_8x16i, rec(numargs := 0));
Class(vzero_4x32f, vop_new, exp_4x32f, rec(numargs := 0));
Class(vzero_2x64f, vop_new, exp_2x64f, rec(numargs := 0));

# Subvec ---------------------------
Class(promote_spu8x16i, vbinop_new, exp_8x16i);
Class(promote_spu4x32f, vbinop_new, exp_4x32f);
Class(promote_spu2x64f, vbinop_new, exp_2x64f);

Class(extract_spu8x16i, vbinop_new, exp_8x16i, rec(computeType := self >> TInt));
Class(extract_spu4x32f, vbinop_new, exp_4x32f, rec(computeType := self >> TReal));
Class(extract_spu2x64f, vbinop_new, exp_2x64f, rec(computeType := self >> TReal));

Class(insert_spu8x16i, vop_new, exp_8x16i, rec(numargs := 3));
Class(insert_spu4x32f, vop_new, exp_4x32f, rec(numargs := 3));
Class(insert_spu2x64f, vop_new, exp_2x64f, rec(numargs := 3));

Class(vsplat_8x16i, vloadop_new, exp_8x16i, rec(numargs := 1));
Class(vsplat_4x32f, vloadop_new, exp_4x32f, rec(numargs := 1));
Class(vsplat_2x64f, vloadop_new, exp_2x64f, rec(numargs := 1));

# Binary ---------------------------
# VA: This breaks bin_or, looks like something unfinished so commented out.
# Class(bin_or, vbinop_new, exp_8x16i);
# Class(bin_or, vbinop_new, exp_4x32f);
# Class(bin_or, vbinop_new, exp_2x64f);

# Rotates ---------------------------
Class(slqwbyte_spu4x32f, vop_new, exp_4x32f, rec(numargs := 1));

Class(rlmaskqwbyte_spu4x32f, vop_new, exp_4x32f, rec(numargs := 1));

# Binary shuffle -------------------
#NOTE: What are sparams, params, semantic, and permparams?
#NOTE: Can we combine all these perms into the same thing somehow?
Class(vperm_8x16i_spu, vbinop_8x16i_spu, rec(
    semantic := (in1, in2, p) -> vpermop(in1, in2, p, 8),
    params := self >> sparams(8, 16),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2], rch[3].p]),
    permparams := aperm
));

Class(vperm_4x32f_spu, vbinop_4x32f_spu, rec(
    semantic := (in1, in2, p) -> vpermop(in1, in2, p, 4),
    params := self >> sparams(4, 8),

    #HACK: small hack: it'd be nice to not define from_rChildren explicitly
    #here. We have to do it though, because the last param is a perm
    #(vparam_spu) type, but the object must be created with a List, and not a
    #vparam_spu.

    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2], rch[3].p]),
    permparams := aperm
));

Class(vperm_2x64f_spu, vbinop_2x64f_spu, rec(
    semantic := (in1, in2, p) -> vpermop(in1, in2, p, 2),
    params := self >> sparams(2, 4),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2], rch[3].p]),
    permparams := aperm
));

# Unary shuffle --------------------
Class(vuperm_8x16i_spu, vunbinop_spu,
    rec(binop := vperm_8x16i_spu,
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2].p])
));

Class(vuperm_4x32f_spu, vunbinop_spu,
    rec(binop := vperm_8x16i_spu,
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2].p])
));

Class(vuperm_2x64f_spu, vunbinop_spu,
    rec(binop := vperm_8x16i_spu,
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2].p])
));


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

# NOTE: too slow
PackCode := function(c)
     local pulled, decls, datas, dims;
     if IsBound(c.dimensions) then dims := c.dimensions; fi;
     pulled := PullBU(Copy(c), decl, e -> e.cmd, e -> e.vars);
     decls := Set(Concatenation(pulled[1]));
     c := pulled[2];

     pulled := Pull(Copy(c), data, e -> e.cmd, e -> [e.var, e.value]);
     datas := pulled[1];
     c := pulled[2];

     c := SubstBottomUp(c, chain, e -> e.flatten());
     c := FoldL(datas, (cod,d) -> data(d[1], d[2], cod), decl(decls, c));
     if IsBound(dims) then c.dimensions := dims; fi;
     return c;
end;

FoldIf := c -> SubstTopDown(c,
    @(1).target(IF).cond(e->IsValue(e.cond)),
    e -> When(e.cond.v=0, e.else_cmd, e.then_cmd));

DeclareHidden := function(code)
    local free, v, dims;
    free := code.free();
    if IsBound(code.dimensions) then dims := code.dimensions; fi;
    for v in free do
        if IsBound(v.value) then code := data(v, v.value, code); fi;
    od;
    if IsBound(dims) then code.dimensions := dims; fi;
    return code;
end;

# This eliminates aliasing between subexpressions of different commands
# (ie pointers can alias, and this is bad)
UntangleChain := c -> SubstTopDownNR(c, chain, e -> chain(List(e.cmds, Copy)));

# reduce complex constants with im(c)=0 to float
_prepConst := e -> Cond(
    e.t <> TComplex, e,
    IsCyc(e.v) and Im(e.v)=0, TDouble.value(e.v),
    IsComplex(e.v) and ImComplex(e.v) = 0, TDouble.value(ReComplex(e.v)),
    e);

Class(_dropTRealInRefs, RuleSet);
RewriteRules(_dropTRealInRefs, rec(
    TReal_left  := ARule(ListClass, [ [@(1, TPtr), @(2, AtomicTyp, x->x=TReal), ...],
        [@(3, TPtr, x->x.alignment=@(1).val.alignment), T_Real, ...]],
        e -> [@(3).val]),
    TReal_right := ARule(ListClass, [ [@(1, TPtr), T_Real, ...],
        [@(2, TPtr), @(3, AtomicTyp, x->x=TReal and @(1).val.alignment=@(2).val.alignment), ...]],
        e -> [@(1).val]),
    TVect_TReal_left  := ARule(ListClass, [ [@(1, TPtr), [TVect, @(2, AtomicTyp, x->x=TReal), @(3)], ...],
        [@(4, TPtr), [TVect, T_Real, @(5).cond(x->x=@(3).val and @(1).val.alignment=@(4).val.alignment)], ...]],
        e -> [@(4).val]),
    TVect_TReal_right := ARule(ListClass, [ [@(1, TPtr), [TVect, T_Real, @(2)], ...],
        [@(3, TPtr), [TVect, @(4, AtomicTyp, x->x=TReal), @(5).cond(x->x=@(2).val and @(1).val.alignment=@(3).val.alignment)], ...]],
        e -> [@(1).val]),
));

#F Compile(<code>)
#F
#F Fully unroll and optimize <code>
#F
Class(Compile, rec(
    datas := 0,
    decls := 0,
    refs := 0,

    status := Ignore,
    timingStatus := Ignore,

    pullDataDeclsRefs := meth(self, code)
        local pulled, c, d, p, id, typecasts, tcast, ref, loopvars, doNotScalarize;
        self.free := code.free();
        pulled := PullBU(code, @(1, [decl, data, nth, deref]),
            e -> When(IsBound(e.cmd), e.cmd, e), e -> e);

        self.decls := Set([]);
        self.datas := tab();
        self.refs  := tab();

        loopvars := Pull(code, @@(1).cond( (x, cx)->IsLoop(x) ),
            e -> e, (cx, e) -> e.var)[1];

        doNotScalarize := (ref) -> ref=false or Collect(ref, @(1, [var, param], x -> (IsLoopIndex(x) and not(x in loopvars)) or x _is param))<>[];

        for p in pulled[1] do
            if   ObjId(p)=decl then UniteSet(self.decls, p.vars);
            elif ObjId(p)=data then self.datas.(p.var.id) := p.value; if IsArrayT(p.var.t) then p.var.value := p.value; fi;
            # nth, deref
            elif IsVar(p.loc) then
                id := p.loc.id;
                if not IsBound(self.refs.(id)) then self.refs.(id) := Set([]); fi;
                AddSet(self.refs.(id), TPtr(p.t));
                if doNotScalarize(p) then # prevent scalarization when there is a dependency on outer loop variable or param
                    AddSet(self.refs.(id), TPtr(TVoid));
                fi;
            fi;
        od;

        # Pull out type casts and figure out granularity of accesses to given variable
        # the complicated condition tries to figure out <..what?..>
        typecasts := Pull(code,
            @@(1,var, (e,cx) -> IsVar(e) and IsArrayT(e.t) and IsBound(cx.tcast) and
                                cx.tcast<>[] and Last(cx.tcast).args[2].t in [e.t, e.t.toPtrType()] ),
            e -> e,
            (cx, e) -> [e, Last(cx.tcast), Cond(IsBound(cx.nth) and Length(cx.nth)>0, Last(cx.nth), false)])[1];

        for p in typecasts do
            [id, tcast, ref] := [p[1].id, p[2], p[3]];
            if not IsBound(self.refs.(id)) then self.refs.(id) := Set([]); fi;
            AddSet(self.refs.(id), tcast.args[1]);
            # below prevents scalarization if pointer arithmetic was used or
            # if there is dependency on outer loop variable or param
            if not IsVar(tcast.args[2]) or doNotScalarize(ref) then
                AddSet(self.refs.(id), TPtr(TVoid));
            fi;
        od;
        # TReal pops up in diffrent places
        for id in UserNSFields(self.refs) do
            self.refs.(id) := _dropTRealInRefs(self.refs.(id));
        od;
        # plugs in data definitions into code, this process could be recursive, since one datavar can depend on another
        return SubstTopDown(pulled[2], @(1,var,e->IsBound(self.datas.(e.id))), e->self.datas.(e.id));
    end,

    scalarizationBarriers := [call, fcall],

    fastScalarize := meth(self,code)
        local d,len,tentry, barriers, typecasts, newlen, newt, newscal;
        barriers := Collect(code, @(1, self.scalarizationBarriers));
        self.doNotScalarize := Union(
            # do not scalarize arrays from barriers (like from function calls)
            Union(List(barriers, c->Collect(c, @(1,var,x->IsArrayT(x.t))))),
            # arrays with vector accesses of different granularity
            Set(Filtered(self.decls, v -> not IsBound(self.refs.(v.id)) or
                    (IsBound(self.refs.(v.id)) and (Length(self.refs.(v.id)) > 1 or ForAny(self.refs.(v.id), IsUnalignedPtrT))) or
                    (IsBound(v.doNotScalarize) and v.doNotScalarize)))
        );

        self.scalarized := Set([]);
        newscal := [];
        for d in Difference(self.decls, self.doNotScalarize) do
            newt := self.refs.(d.id)[1];
            if IsArrayT(d.t) and IsPtrT(newt) then
                newt := newt.t; # base type of ptr
                newlen := d.t.size * When(IsVecT(d.t.t), d.t.t.size, 1) / When(IsVecT(newt), newt.size, 1);
                if IsSymbolic(newlen) or IsValue(newlen) then newlen := newlen.ev(); fi;
                tentry := List([0..newlen-1], i -> var.fresh_t("scal", newt));
                Append(newscal, tentry);
                d.value := Value(TArray(newt, newlen), tentry);
            fi;
        od;
        self.scalarized := Set(newscal);

        return code;
    end,

    # NOTE: make this more general, in particular, if variable does not appear
    # on the left-hand side of the assignment it won't be declared, scalar
    # variables won't be declared if they are only used inside call() (pathological case)
    # Vectors can in fact be only used inside call, so this situtation is handled correctly.
    declareVars := meth(self, code)
        local vars, vects;
        vars  := Filtered(Set(ConcatList(Collect(code, @.cond(e->IsCommand(e) and IsBound(e.op_out))), e->e.op_out())), IsVar);
        vects := Filtered(code.free(), x -> IsArray(x.t));

        IntersectSet(vects, self.decls);
	SubtractSet(vects, self.free);
	SubtractSet(vars,  self.free);

        if Length(vars) > 0 then code := decl(vars, code); fi;
        if Length(vects) > 0 then code := decl(vects, code); fi;
        return code;
    end,

    # Old and slow scalarizer no longer works, but old scalarizer did not make an
    # assumption of "constant geomery code" and the current scalarizer does make that
    # assumption. It is becoming a limitation for certain applications, so we might need
    # to redesign our current fast scalarizer to be more like the old one.
    #
    #D scalarize := (self, c) >> Scalarize(c, self.decls),

    showTimes := meth(self)
        local i;
        Print("times := [\n");
        for i in [1..Length(self.times)] do
	    PrintEval("$1,  $2,\n", i, StringDouble("%.3g", self.times[i]));
	od;
        Print("];\n");
    end,

    __call__ := meth(self, c, opts)
        local dims, root, stage, compileStrategy, t, i;

        if IsBound(c.dimensions) then dims := c.dimensions; fi;
        if IsBound(c.root) then root := c.root; fi;

        if opts.printWebMeasure then Print("web:measure\n"); fi;
        self.curcode := [Copy(c)];

        # self.times keeps compilation time information to be used for profiling
        if not IsBound(self.times) or Length(self.times)<>Length(opts.compileStrategy) then
            self.times := Replicate(Length(opts.compileStrategy), 0.0); fi;

        for i in [1..Length(opts.compileStrategy)] do
            stage := opts.compileStrategy[i];
            if IsMeth(stage) and IsCallableN(stage, 2) then
                [c,t] := UTimedAction(stage(self, c, opts));
            elif IsMeth(stage) then
                [c,t] := UTimedAction(stage(self, c));
            elif IsCallableN(stage, 2) then
                [c,t] := UTimedAction(stage(c, opts));
            else
                [c,t] := UTimedAction(stage(c));
            fi;
            Add(self.curcode, Copy(c));
            self.times[i] := self.times[i] + t;
	    self.timingStatus(i, stage, t); # print timing info

            if opts.printWebMeasure then Print("web:measure\n"); fi;
        od;

        if IsBound(dims) then c.dimensions := dims; fi;
        if IsBound(root) then c.root := root; fi;
        return c;
    end
));


# GAP is case sensitive
ThreeDogs := function()
	local dog, Dog, DOG;
	dog := "Benjamin";
	Dog := "Samba";
	DOG := "Bernie";
	if dog = DOG then
		Print("There is just one dog named ", dog, "\n");
	else
		Print("The three dogs are named ", dog, ", ", Dog, " and ", DOG, "\n");
	fi;
end;

ThreeDogs();
# The three dogs are named Benjamin, Samba and Bernie


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

Import(search);

#   These functions merge multiple hashtables into a single one
AllHashEntries := function(table)
    local e, i, res;
    res := [];
    for e in Filtered(table.entries, IsList) do
        for i in e do
            if i.data <> [] then Add(res, i); fi;
        od;
    od;
    return res;
end;

AddMergedHashTables := function(target, newhashs)
    local e, v, h, i;
    for e in Flat(List(newhashs, i->AllHashEntries(i))) do
        v := [];
        for h in newhashs do
            Add(v, HashLookup(h, e.key));
        od;
        v := Filtered(v, i -> i <> [] and i <> false);
        if v <> [] then
            Sort(v, (j,k) -> j[1].measured < k[1].measured);
            HashAdd(target, e.key, v[1]);
        fi;
    od;
end;

#   Build all base cases for a given SIMD ISA.
#   Currently only L(2v, 2), L(2v, v), L(v^2, v)
#
SIMD_ISA_DB.buildBases := meth(self, isa)
    local rebind, t, v, tags, cxtags, t1, t2, rt, brules, common, rset1, rset2, tab1, tab2, e, h;

    if self.verbose then Print("Building bases for ", isa, "\n"); fi;
    self.hash_rebuilt := true;
    v := isa.v;
    tags := isa.getTags();
    cxtags := isa.getTagsCx();

    # == do TL base cases======================================
    # TL usually is not measured in DP
    rebind := IsBound(TL.doNotMeasure) and TL.doNotMeasure;
    if rebind then
        TL.doNotMeasure := false;
    fi;

    brules := paradigms.tSPL_Globals.getDPOpts().breakdownRules;
    common := [ SIMD_ISA_Bases1, SIMD_ISA_Bases2, IxLxI_kmn_n, IxLxI_kmn_km, paradigms.vector.breakdown.IxLxI_vtensor ];

    rset1 := CopyFields(brules, rec(TL := Concat(common, [IxLxI_IxLxI_up])));
    rset2 := CopyFields(brules, rec(TL := Concat(common, [IxLxI_IxLxI_down])));

    tab1 := HashTableDP();
    for t in SIMD_ISA_DB.getBases(isa) do
#    [ TL(2*v,v,1,1).withTags(tags), TL(2*v,2,1,1).withTags(tags), TL(v*v,v,1,1).withTags(tags), TL(v*v/4,v/2,1,2).withTags(tags) ]
        t1 := DP(t, rec(measureFunction := VCost, verbosity := 0, hashTable := tab1, globalUnrolling := true),
                CopyFields(isa.splopts, rec(breakdownRules := rset1, dataType := "no default", baseHashes := [], globalUnrolling := 10000)));
    od;

    tab2 := HashTableDP();
    for t in SIMD_ISA_DB.getBases(isa) do
#    [ TL(2*v,v,1,1).withTags(tags), TL(2*v,2,1,1).withTags(tags), TL(v*v,v,1,1).withTags(tags), TL(v*v/4,v/2,1,2).withTags(tags) ]
        t2 := DP(t, rec(measureFunction := VCost, verbosity := 0, hashTable := tab2, globalUnrolling := false),
                CopyFields(isa.splopts, rec(breakdownRules := rset2, dataType := "no default", baseHashes := [], globalUnrolling := 10000)));
    od;
    AddMergedHashTables(self.hash, [tab1, tab2]);
    # restore TL
    if rebind then
        TL.doNotMeasure:=true;
    fi;

    # == do other base cases===================================
    # do other base cases
    #
    # none here yet :(
    #

    # == final check - did all work? ==========================
    for e in self.getBases(isa) do
        h := HashLookup(self.hash, e);
        if  h=false or h=[] then Print(e, " could not be built\n"); fi;
    od;
end;


IsChar('a');
# true
IsString("abc");
# true
IsString('a');
# false
IsChar("a");
# false