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

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

#######################################################################################
# SSE2 2-way 64-bit floating-point instructions
#######################################################################################

vzero_2x64f_const := TVect(TReal, 2).zero();
vzero_2x64f := () -> vzero_2x64f_const;

# Load
#
SSE_LDST(
    Class(vload1sd_2x64f, VecExp_2.unary()),
    Class(vload_1l_2x64f, VecExp_2.binary()),
    Class(vload_1h_2x64f, VecExp_2.binary()),
    Class(vloadu_2x64f,   VecExp_2.unary())
);

# Store
#
SSE_LDST(
    Class(vstore_1l_2x64f, VecStoreCommand.binary()),
    Class(vstore_1h_2x64f, VecStoreCommand.binary()),
    Class(vstoreu_2x64f,   VecStoreCommand.binary())
);

# Binary shuffle
#
Class(vunpacklo_2x64f, VecExp_2.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 2, 1)));

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

Class(vshuffle_2x64f, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 2, 1),
    params := self >> sparams(2,2),
    ev := self >> self.t.value(self.semantic(self._vval(1),self. _vval(2), self.args[3].p)),
));

# Unary shuffle, sign change
#
Class(vushuffle_2x64f, VecExp_2.unaryFromBinop(vshuffle_2x64f));

Class(chslo_2x64f,     VecExp_2.unary(), rec(
    ev := self >> let(v := self.args[1], v * self.t.value([-1.0, 1.0])),
));

Class(chshi_2x64f,     VecExp_2.unary(), rec(
    ev := self >> let(v := self.args[1], v * self.t.value([1.0, -1.0])),
));

# SSE3 new addsub and hadd instructions
#
Class(addsub_2x64f, VecExp_2.binary(), rec(
    ev := self >> add(self.args[1], chslo_2x64f(self.args[2]))
));
Class(hadd_2x64f,   VecExp_2.binary());

#######################################################################################
# SSE2 2-way 64-bit integer instructions
# NOTE: use TFixedPt instead of TReal in as the type in below instructions
#######################################################################################

vzero_2x64i_const := TVect(TReal, 2).zero();
vzero_2x64i := () -> vzero_2x64i_const;

# Binary shuffle
#
Class(vunpacklo_2x64i, VecExp_2.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 2, 1)
));

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

Class(vshuffle_2x64i, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 2, 1),
    params := self >> sparams(2,2)
));

# Unary shuffle, sign change
#
Class(vushuffle_2x64i, VecExp_2.unaryFromBinop(vshuffle_2x64i));
Class(chslo_2x64i,     VecExp_2.unary());
Class(chshi_2x64i,     VecExp_2.unary());

# cmpge_2x64f
Class(cmpge_2x64f,  VecExp_2.binary());
Class(cmple_2x64f,  VecExp_2.binary());
Class(cmpeq_2x64f,  VecExp_2.binary());

#######################################################################################
# SSE2 2-way 32-bit floating-point instructions
# this architecture simulates 2-way inside 4-way
#######################################################################################

vzero_2x32f_const := TVect(TReal, 2).zero();
vzero_2x32f := () -> vzero_2x32f_const;

# Load
#
SSE_LDST(
    Class(vload1sd_2x32f, VecExp_2.unary()),
    Class(vload_1l_2x32f, VecExp_2.binary()),
    Class(vload_1h_2x32f, VecExp_2.binary()),
    Class(vload_2x32f,    VecExp_2.binary()),
    Class(vloadu_2x32f,   VecExp_2.unary())
);

# Store
#
SSE_LDST(
    Class(vstore_1l_2x32f, VecExpCommand),
    Class(vstore_1h_2x32f, VecExpCommand),
    Class(vstore_2x32f,    VecExpCommand),
    Class(vstoreu_2x32f,   VecExpCommand)
);

# Binary shuffle
#
Class(vunpacklo_2x32f, VecExp_2.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 2, 1)
));

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

Class(vshuffle_2x32f, VecExp_2.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 2, 1),
    params := self >> sparams(2,2)
));

# Unary shuffle, sign change
#
Class(vushuffle_2x32f, VecExp_2.unaryFromBinop(vshuffle_2x32f));
Class(chslo_2x32f, VecExp_2.unary());
Class(chshi_2x32f, VecExp_2.unary());

# SSE3 new addsub and hadd instructions
#
Class(addsub_2x32f, VecExp_2.binary());
Class(hadd_2x32f,   VecExp_2.binary());

#######################################################################################
#   SSE 4-way 32-bit float instructions
#######################################################################################

vzero_4x32f_const := TVect(T_Real(32), 4).zero();
vzero_4x32f := () -> vzero_4x32f_const;

# Load
#
SSE_LDST(
    Class(vload1_4x32f,   VecExp_4.unary()),
    Class(vload_2l_4x32f, VecExp_4.binary()),
    Class(vload_2h_4x32f, VecExp_4.binary()),
    Class(vloadu_4x32f,   VecExp_4.unary()),
    Class(vloadu2_4x32f,  VecExp_4.unary()),
    Class(vinsert_4x32f,  VecExp_4.binary())
);

# Store
#
SSE_LDST(
    Class(vstore1_4x32f,   VecStoreCommand.binary()),
    Class(vstore_2l_4x32f, VecStoreCommand.binary()),
    Class(vstore_2h_4x32f, VecStoreCommand.binary()),
    Class(vstoreu_4x32f,   VecStoreCommand.binary()),
    Class(vstoreu2_4x32f,  VecStoreCommand.binary()), # cprint: self.loc.svcprint(16)
    Class(vstoremsk_4x32f, VecStoreCommand.binary()),   # cprint: self.loc.svcprint(8)
    Class(vextract_4x32f,  VecStoreCommand.binary())
);

# Binary shuffle
#
Class(vunpacklo_4x32f, VecExp_4.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 4, 1),
));

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

Class(vshuffle_4x32f,  VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 4, 1),
    params := self >> sparams(4,4),
    permparams := iperm4,
    ev := self >> self.t.value(self.semantic(self._vval(1),self._vval(2), self.args[3].p)),
));

# Unary shuffle
#
Class(vushuffle_4x32f, VecExp_4.unaryFromBinop(vshuffle_4x32f), rec(
    ev := self >> self.t.value(self.semantic(self._vval(1), self.args[2].p)),
));

Class(chshi_4x32f,  VecExp_4.unary(), rec(
    ev := self >> let( v := self.args[1], v * self.t.value([1.0, -1.0, 1.0, -1.0]) )
));

Class(chslo_4x32f,  VecExp_4.unary(), rec(
    ev := self >> let( v := self.args[1], v * self.t.value([-1.0, 1.0, -1.0, 1.0]) )
));

Class(vldup_4x32f,  VecExp_4.unary(), rec(
    ev := self >> let( v := self._vval(1), self.t.value([v[1], v[1], v[3], v[3]]) )
));
Class(vhdup_4x32f,  VecExp_4.unary(), rec(
    ev := self >> let( v := self._vval(1), self.t.value([v[2], v[2], v[4], v[4]]) )
));

# horizontal add
Class(alignr_4x32f, VecExp_4.binary());

#######################################################################################
#   SSE 4-way 32-bit integer instructions
#######################################################################################

vzero_4x32i_const := TVect(TReal, 4).zero();
vzero_4x32i := () -> vzero_4x32i_const;

Class(vloadu_4x32i, VecExp_4.unary());
Class(vstoreu_4x32i, VecExpCommand);

# Binary shuffle
#
Class(vunpacklo_4x32i, VecExp_4.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 4, 1)));

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

Class(vpacks_4x32i, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> pack_semantic(in1, in2, 4),
    computeType := self >> TVect(T_Int(16), 8)));

Class(vshuffle_4x32i, VecExp_4.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 4, 1),
    params := self >> sparams(4,4),
    permparams := iperm4
));

# Unary shuffle
#
Class(vushuffle_4x32i, VecExp_4.unaryFromBinop(vshuffle_4x32i));

# for Viterbi
Class(interleavedmask_4x32i, VecExp_4.binary(), rec(
    computeType := self >> TUChar
));

#_mm_testz_si128
Class(testz_4x32i, VecExp_4.binary(), rec(
    computeType := self >> TInt
));

#_mm_testc_si128
Class(testc_4x32i, VecExp_4.binary(), rec(
    computeType := self >> TInt
));

#_mm_testnzc_si128
Class(testnzc_4x32i, VecExp_4.binary(), rec(
    computeType := self >> TInt
));

# SSE3 new addsub and hadd instructions
#
Class(addsub_4x32f, VecExp_4.binary(), rec(
    ev := self >> add(self.args[1], chslo_4x32f(self.args[2]))
));

Class(hadd_4x32f,   VecExp_4.binary());

# type conversion of lower two elements from 4x32f to 2x64f
Class(vcvt_64f32f, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Real(64), 2)
));

# type conversion of 2x64f to lower two of 4x32f
Class(vcvt_32f64f, VecExp_2.unary(), rec(
    computeType := (self) >> TVect(T_Real(32), 4)
));

# type conversion 4x32i to 4x32f
Class(vcvt_4x32_i2f, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Real(32), 4)
));

# type conversion 4x32f to 4x32i, rounding acording MXCSR register rules
Class(vcvt_4x32_f2i, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Int(32), 4)
));

# type conversion 4x32f to 4x32i, truncation toward zero
Class(vcvtt_4x32_f2i, VecExp_4.unary(), rec(
    computeType := (self) >> TVect(T_Int(32), 4)
));

#######################################################################################
#   SSE2 8-way 16-bit integer instructions
#######################################################################################

vzero_8x16i_const := TVect(TReal, 8).zero();
vzero_8x16i := () -> vzero_8x16i_const;

# SSSE3 sign change instructions
Class(chs_8x16i, VecExp_8.binary());

# Load
#
SSE_LDST(
    Class(vload1_8x16i, VecExp_8.ternary()),
    Class(vload2_8x16i, VecExp_8.unary()),
    Class(vload4_8x16i, VecExp_8.unary()),
    Class(vloadu_8x16i, VecExp_8.unary())
);

# Store
#
SSE_LDST(
    Class(vextract1_8x16i, VecExpCommand, rec(numargs := 2)),
    Class(vextract2_8x16i, VecExpCommand, rec(numargs := 1)),
    Class(vstoreu_8x16i,   VecExpCommand),
    Class(vstore4_8x16i,   VecExpCommand),
    Class(vstoremsk_8x16i, VecExpCommand)
);

# Binary shuffle
#
Class(vunpacklo_8x16i, VecExp_8.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 8, 1)));

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

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

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

Class(vunpacklo4_8x16i, VecExp_8.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 8, 4)));

Class(vunpackhi4_8x16i, VecExp_8.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 8, 4)));

Class(vpacks_8x16i, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> pack_semantic(in1, in2, 16),
    computeType := self >> TVect(T_Int(8), 16)));

Class(vpackus_8x16i, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> pack_semantic(in1, in2, 16),
    computeType := self >> TVect(T_UInt(8), 16)));

Class(vshuffle2_8x16i, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 8, 2),
    params := self >> sparams(4,4),
    permparams := iperm4
));

Class(vshuffle4_8x16i, VecExp_8.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 8, 4),
    params := self >> sparams(2,2),
));

# Unary shuffle
#
Class(vushuffle2_8x16i, VecExp_8.unaryFromBinop(vshuffle2_8x16i), rec(
    params := self >> sparams(4,4),
    permparams := iperm4,
));

Class(vushufflelo_8x16i, VecExp_8.unary(), rec(
    params := self >> sparams(4,4),
    permparams := iperm4,
    semantic := (in1, p) -> shufflelo(in1, p, 8, 1)));  #Complicated, buggy?

Class(vushufflehi_8x16i, VecExp_8.unary(), rec(
    params := self >> sparams(4,4),
    permparams := iperm4,
    semantic := (in1, p) -> shufflehi(in1, p, 8, 1)));  #Complicated, buggy?

# SSSE3 unary shuffle
Class(vushuffle_8x16i, VecExp_8.binary());

# for Viterbi
Class(interleavedmask_8x16i, VecExp_8.binary(), rec(
        computeType := self >> TSym("short int")
));

Class(alignr_8x16i, VecExp_8.binary());

# cmplt_8x16i
Class(cmplt_8x16i,  VecExp_8.binary());

#_mm_testz_si128
Class(testz_8x16i, VecExp_8.binary(), rec(
    computeType := self >> TInt
));
#_mm_testc_si128
Class(testc_8x16i, VecExp_8.binary(), rec(
    computeType := self >> TInt
));

#_mm_testnzc_si128
Class(testnzc_8x16i, VecExp_8.binary(), rec(
    computeType := self >> TInt
));

#######################################################################################
#   SSE2 16-way 8-bit integer instructions
#######################################################################################

Class(vushuffle_16x8i, VecExp_16.unary(), rec(
    params := self >> sparams(4,4),
));

# Load/Store
#
SSE_LDST(
    Class(vloadu_16x8i,    VecExp_16.unary()),
    Class(vstoreu_16x8i,   VecExpCommand),
    Class(vstoremsk_16x8i, VecExpCommand)
);

# Binary shuffle
#
Class(vunpacklo_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 1)));

Class(vunpackhi_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 1)));

Class(vunpacklo2_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 2)));

Class(vunpackhi2_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 2)));

Class(vunpacklo4_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 4)));

Class(vunpackhi4_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 4)));

Class(vunpacklo8_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 8)));

Class(vunpackhi8_16x8i, VecExp_16.binary(), _ev_binop_semantic_mixin, rec(
    semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 8)));

Class(vshuffle4_16x8i, VecExp_16.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 16, 4),
    params := self >> sparams(4,4),
    permparams := iperm4
));

Class(vshuffle8_16x8i, VecExp_16.binary(), rec(
    semantic := (in1, in2, p) -> shuffle(in1, in2, p, 16, 8),
    params := self >> sparams(2,2),
));

# Unary shuffles
#
Class(vushuffle4_16x8i, vushuffle_16x8i, rec(
    semantic := (in1, p) -> shuffle(in1, in1, p, 16, 4)));

Class(vushufflelo2_16x8i, vushuffle_16x8i, rec(
    semantic := (in1, p) -> shufflelo(in1, p, 16, 2)));

Class(vushufflehi2_16x8i, vushuffle_16x8i, rec(
    semantic := (in1, p) -> shufflehi(in1, p, 16, 2)));

# Viterbi
#
Class(interleavedmaskhi_16x8i, VecExp_8.binary(), rec(
    computeType := self >> TSym("short int")));

Class(interleavedmasklo_16x8i, VecExp_8.binary(), rec(
    computeType := self >> TSym("short int")));

Class(average_16x8i, VecExp_16.binary());

Class(vmovemask_16x8i, VecExp_16.unary(), rec(
    computeType := self >> T_UInt(16)));

Class(vcastizxlo, castizx, rec(
    ev := self >> vtakelo(self.args[1].t.toUnsigned().value(self.args[1].ev()))
));

Class(vcastizxhi, castizx, rec(
    ev := self >> vtakehi(self.args[1].t.toUnsigned().value(self.args[1].ev()))
));

Class(vcastuzxlo, castuzx, rec(
    ev := self >> vtakelo(self.args[1].t.toUnsigned().value(self.args[1].ev()))
));

Class(vcastuzxhi, castuzx, rec(
    ev := self >> vtakehi(self.args[1].t.toUnsigned().value(self.args[1].ev()))
));


a := [1 .. 4];
b := ShallowCopy(a);

# Apply and replace values
Apply(a, n -> n*n);
a;
# [ 1, 4, 9, 16 ]

# Apply and don't change values
List(b, n -> n*n);
# [ 1, 4, 9, 16 ]

# Apply and don't return anything (only side effects)
Perform(b, Display);
1
2
3
4

b;
# [ 1 .. 4 ]


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

RandInput := () -> Complex(
    RandomList([1..2^27])/(1.0*2^27),
    RandomList([1..2^27])/(1.0*2^27));

RandValExp := function(exp)
    local e, rargs;
    if IsBound(exp.randVal) then
	return exp.randVal;

    elif IsValue(exp) then
	exp.randVal := exp.v;
	return exp.randVal;

    elif IsLoc(exp) then
	exp.randVal := RandInput();
	#Print("RandInput : ", exp, " = ", exp.randVal, "\n");
	return exp.randVal;
    else
	rargs := List(exp.args, RandValExp);
	e := ApplyFunc(ObjId(exp), rargs);
	e := e.ev();
	Constraint(not IsExp(e) and not IsLoc(e));
	exp.randVal := e;
	return exp.randVal;
    fi;
end;

RandValAssign := function(cmd)
    local r;
    if IsBound(cmd.loc.randVal) then return cmd.loc.randVal;
    elif IsBound(cmd.exp.randVal) then
	cmd.loc.randVal := cmd.exp.randVal;
	return cmd.loc.randVal;
    else
	r := RandValExp(cmd.exp);
	cmd.exp.randVal := r;
	cmd.loc.randVal := r;
	return r;
    fi;
end;

Class(RCSE, rec(
    cseLookup := (self, randVal) >> HashLookup(self.csetab, V(randVal)),

    cseAdd := meth(self, cmd)
        local op, ent, v;
	if not IsBound(cmd.exp.isExpComposite) then return; fi;
	HashAdd(self.csetab, V(cmd.loc.randVal), cmd.loc);
	HashAdd(self.csetab, V(-cmd.loc.randVal), neg(cmd.loc));
    end,

    cseCmd := meth(self, cmd)
        local lkup;
	RandValAssign(cmd);
	lkup := self.cseLookup(cmd.loc.randVal);
	if lkup <> false then
	    cmd.exp := lkup;
	else
	    self.cseAdd(cmd);
	fi;
    end,

    remove_node := (self, exp) >> Chain(
	HashDelete(self.csetab, V(exp.randVal)),
	HashDelete(self.csetab, V(-exp.randVal))),

    node := meth(self, t, exp)
        local lkup, v;
	if IsValue(exp) or IsLoc(exp) then
	    RandValExp(exp);
	    return exp;
	fi;

	exp := ApplyFunc(ObjId(exp), List(exp.args, a->self.node(t, a)));
	RandValExp(exp);

	lkup := self.cseLookup(exp.randVal);
	if lkup <> false then
	    return lkup;
	else
	    v := var.fresh_t("t", t);
	    v.randVal := exp.randVal;
	    v.def := assign(v, exp);
	    self.cseAdd(v.def);
	    return v;
	fi;
    end,

    flush := meth(self)
        self.csetab := HashTable( (val,size) -> val.t.hash(val.v, size) );
    end,

    __call__ := meth(self, code)
        self.csetab := HashTable( (val,size) -> val.t.hash(val.v, size) );
	DoForAll(Collect(code, assign), cmd -> self.cseCmd(cmd));
	return code;
    end
));


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

Class(LinearModel, rec(
    __call__ := (self, k, d) >> CopyFields(self, rec(k := k, d := d)),
    d := 0,
    k := 1,
    eval := (self, x) >> self.d + self.k * x
));

Class(ExpModel, rec(
    __call__ := (self, alpha, tau) >> CopyFields(self, rec(alpha := alpha, tau := tau)),
    alpha := 0.1,
    tau := 5,
    eval := (self, x) >> (self.alpha + (1 - d_exp(-self.tau * x))) / (1+self.alpha)
));

Class(ScratchModel, rec(
    config := rec(
        loadModel := (numPkts, pktSize) -> numPkts * pktSize / (0.2 * ExpModel(0.1, 0.2).eval(pktSize)),
        storeModel := (numPkts, pktSize) -> 1.1 * numPkts * pktSize / (0.2 * ExpModel(0.1, 0.2).eval(pktSize)),
        computeModel := x->LinearModel(1.1, 5).eval(x)
    ),
    getKernels := (self, spl) >>  List(Collect(self.updateInfo(spl), LSKernel), i->i.info),
    updateInfo := (self, spl) >>  SubstTopDown(spl, [@(1, Compose), @(2, DMAScat), @(3, LSKernel), @(4, DMAGath)],
        (e,cx) -> @(2).val *
            LSKernel(@(3).val.child(1), rec(
                opcount := @(3).val.info.opcount,
                loadFunc := @(4).val.func,
                storeFunc := @(2).val.func,
                free := When(IsBound(cx.ISum), Set(Concat(List(cx.ISum, i->i.var))), Set([])))) *
            @(4).val),

    getPacketData := meth(self, func)
        local domain, pktSize, numPkts;

        domain := func.domain();
        pktSize := 1;
        if ObjId(func) = fTensor and ObjId(Last(func.children())) = fId then
            pktSize := Last(func.children()).domain();
        fi;
        numPkts := domain / pktSize;

        return [pktSize, numPkts];
    end,

    modelStat := meth(self, spl)
        local kernels, stat, numPkts, pktSize, kernel, iter, func, n,
            flops, bw, loadcycles, storecycles, cpucycles, cycles;

        kernels := self.getKernels(spl);
        stat := [];

        for kernel in kernels do
            flops := kernel.opcount;
            cpucycles := self.config.computeModel(flops);

            n := kernel.loadFunc.domain();
            [pktSize, numPkts] := self.getPacketData(kernel.loadFunc);
            loadcycles := self.config.loadModel(numPkts, pktSize);
            [pktSize, numPkts] := self.getPacketData(kernel.storeFunc);
            storecycles := self.config.storeModel(numPkts, pktSize);
            iter := Product(List(kernel.free, i->i.range));
            Add(stat, rec(
                iter := iter,
                kernelcycles := Maximum(loadcycles, storecycles, cpucycles),
                loadcycles := loadcycles,
                storecycles := storecycles,
                cpucycles := cpucycles,
                balance := cpucycles / Maximum(loadcycles, storecycles)
            ));

        od;
        return stat;
    end,

    modelRuntime := meth(self, spl)
        local stat, s, runtime, cycles;

        runtime := 0;
        stat := self.modelStat(spl);

        for s in stat do
            cycles := s.loadcycles + s.storecycles + s.iter * s.kernelcycles;
            runtime := runtime + cycles;
        od;
        return When(IsInt(runtime), runtime, IntDouble(runtime));
    end,

    measureFunction := (rt, opts) -> let(s := SumsRuleTree(rt, opts),  paradigms.scratchpad.ScratchModel.modelRuntime(s))
));


#!/usr/bin/gap

# time gap -o 8g hierarchy.gap

# Build clifford groups, up to 3 qubits, and
# search for T operators defined by field extensions......

Print("running hierarchy.gap\n");;

# See:
# https://www.mathstat.dal.ca/~selinger/papers/clifford.pdf

r2 := Sqrt(2);;
ir2 := 1/r2;;

i := [[1, 0], [0, 1]];;
w := [[E(4), 0], [0, E(4)]];;
x := [[0, 1], [1, 0]];;
z := [[1, 0], [0, -1]];;
s := [[1, 0], [0, E(4)]];;
h := [[ir2, ir2], [ir2, -ir2]];;

Cliff1 := Group(w, s, h);; # Order 192
Pauli1 := Group(w, x, z);; # Order 32

for U in Cliff1 do
    found := false;;
    #Udag := Inverse(U);
    #Print("found? ");
    for g in Pauli1 do
        if (U*g*Inverse(U)*Inverse(g) in Pauli1)  then found:=true; break; fi;
    od;
    if not found then Print("Not found\n"); fi;
od;

xi := KroneckerProduct(x, i);;
ix := KroneckerProduct(i, x);;
zi := KroneckerProduct(z, i);;
iz := KroneckerProduct(i, z);;
si := KroneckerProduct(s, i);;
is := KroneckerProduct(i, s);;
hi := KroneckerProduct(h, i);;
ih := KroneckerProduct(i, h);;
wi := KroneckerProduct(w, i);;

cz := [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, -1]];;

Cliff2 := Group(si, is, hi, ih, wi, cz);; # Order 92160
Order(Cliff2);
#for g in Cliff2 do Print(g, "\n"); od;
Phase2 := Group(wi);;
Pauli2 := Group(wi, xi, ix, zi, iz);;

# Works:
for U in Cliff2 do
    found := false;;
    #Udag := Inverse(U);
    #Print("found? ");
    for g in Pauli2 do
        if (U*g*Inverse(U)*Inverse(g) in Pauli2)  then found:=true; break; fi;
    od;
    if not found then Print("Not found\n"); fi;
od;

Hadamard4 := (1/2)*[
    [0, 1, 0, 1],
    [2, 0, 2, 0],
    [0, 1, 0, -1],
    [2, 0, -2, 0]];
#Print(Hadamard4*Hadamard4, "\n");
#Print(Hadamard4, " in Cliff2 ", Hadamard4 in Cliff2, "\n");

a := [
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
    [-1, 0, 0, 0]];;

# Print(a in Cliff2, "\n"); # true

#hom:=EpimorphismFromFreeGroup(Cliff2:names:=["si", "is", "hi", "ih", "wi", "cz"]);
#Print(PreImagesRepresentative(hom, a), "\n"); # si^3*is^3*hi*si^2*ih*is^2*ih*cz*hi*si
#Print(PreImagesRepresentative(hom, a*a), "\n"); # (si^2*hi)^2
#Print(PreImagesRepresentative(hom, a*a*a), "\n"); # si^2*is^3*hi*(si^2*hi*si)^2*si*ih*is^2*ih*cz*hi*si

cls := ConjugacyClass(Cliff2, a);;
#Print(a,               a in cls, "\n");               # true
#Print(a*a,             a*a in cls, "\n");             # false
#Print(a*a*a,           a*a*a in cls, "\n");           # true
#Print(a*a*a*a,         a*a*a*a in cls, "\n");         # false
#Print(a*a*a*a*a,       a*a*a*a*a in cls, "\n");       # true
#Print(a*a*a*a*a*a,     a*a*a*a*a*a in cls, "\n");     # false
#Print(a*a*a*a*a*a*a,   a*a*a*a*a*a*a in cls, "\n");   # true
#Print(a*a*a*a*a*a*a*a, a*a*a*a*a*a*a*a in cls, "\n"); # false
#Print(cz, cz in cls, "\n"); # false

#b := [
#    [1, 0, 0, 0],
#    [0, 1, 0, 0],
#    [0, 0, 0, 1],
#    [0, 0, -1, 0]];;
#Print(cz, cz in ConjugacyClass(Cliff2, b), "\n"); # false
#for b in ConjugacyClass(Cliff2, cz) do Print(b, "\n"); od;

#a1 := [
#    [0, 0, 1, 0],
#    [0, 0, 0, 1],
#    [0, 1, 0, 0],
#    [-1, 0, 0, 0]];;
#Print(a1 in Pauli2, "\n"); # false
#Print(a1 in Cliff2, "\n"); # true
#Print(a1 in cls, "\n");    # true

#Print(Eigenvalues(Cyclotomics, b), "\n"); # fail

#Print(a*a, "\n");
#Print(a*a*a, "\n");
#Print(a*a*a*a, "\n");

# Print(Order(G2));

Hadamard8 := (1/2)*[
    [ 0, 1, 0,-1, 0, 1, 0,-1],
    [ 1, 0, 1, 0, 1, 0, 1, 0],
    [ 0, 1, 0, 1, 0, 1, 0, 1],
    [-1, 0, 1, 0,-1, 0, 1, 0],
    [ 0, 1, 0,-1, 0,-1, 0, 1],
    [ 1, 0, 1, 0,-1, 0,-1, 0],
    [ 0, 1, 0, 1, 0,-1, 0,-1],
    [-1, 0, 1, 0, 1, 0,-1, 0]];

#Print(Hadamard8, "\n");
#Print(Hadamard8*Hadamard8, "\n"); # = identity

U2 := [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]];

in_third_level_2 := function(U2)
    # Is U2 in the third level of the clifford hierarchy ?
    local A;
    for g in Pauli2 do
        A := U2*g*Inverse(U2)*Inverse(g);
        if A in Cliff2 then continue; fi;
        return false; # no
    od;
    return true; # yes
end;;

in_fourth_level_2 := function(U2)
    # Is U2 in the fourth level of the clifford hierarchy ?
    local A;
    for g in Pauli2 do
        A := U2*g*Inverse(U2)*Inverse(g);
        if in_third_level_2(U2) then continue; fi;
        return false; # no
    od;
    return true; # yes
end;;

in_fifth_level_2 := function(U2)
    # Is U2 in the fifth level of the clifford hierarchy ?
    local A;
    for g in Pauli2 do
        A := U2*g*Inverse(U2)*Inverse(g);
        if in_fourth_level_2(U2) then continue; fi;
        return false; # no
    od;
    return true; # yes
end;;

get_level_2 := function(U2)
    if U2 in Phase2 then return "0\c"; fi;
    if U2 in Pauli2 then return "1\c"; fi;
    if U2 in Cliff2 then return "2\c"; fi;
    if in_third_level_2(U2) then return "3\c"; fi;
    if in_fourth_level_2(U2) then return "4\c"; fi;
    if in_fifth_level_2(U2) then return "5\c"; fi;
    return ".\c";
end;;

if true then
    Print("# Where do control 1-qubit Pauli gates live ?\n");
    for g in Pauli1 do
        #Print(g, "\n");
        U2{[3,4]}{[3,4]} := g;
        Print(get_level_2(U2));
    od;
    Print("\n");

    Print("# Where do control 1-qubit clifford gates live ?\n");
    for g in Cliff1 do
        #Print(g, "\n");
        U2{[3,4]}{[3,4]} := g;
        Print(get_level_2(U2));
    od;
    Print("\n");
fi;

# ---------------------------------------------------
#
# 3 qubit gates
#

xii := KroneckerProduct(xi, i);;
ixi := KroneckerProduct(i, xi);;
iix := KroneckerProduct(i, ix);;

zii := KroneckerProduct(zi, i);;
izi := KroneckerProduct(i, zi);;
iiz := KroneckerProduct(i, iz);;

sii := KroneckerProduct(si, i);;
isi := KroneckerProduct(i, si);;
iis := KroneckerProduct(i, is);;

hii := KroneckerProduct(hi, i);;
ihi := KroneckerProduct(i, hi);;
iih := KroneckerProduct(i, ih);;

wii := KroneckerProduct(wi, i);;

icz := KroneckerProduct(i, cz);;
czi := KroneckerProduct(cz, i);;

ca := [
    [1, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 0, 0, -1, 0, 0, 0]];;

d := [
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 1],
    [-1, 0, 0, 0, 0, 0, 0, 0]];;

ca1 := [
    [1, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, -1, 0, 0, 0]];;

cb := [
    [1, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 0, 0, -1, 0, 0, 0],
    [0, 0, 0, 0, 0, -1, 0, 0]];;

cc := [
    [1, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 0, 0, -1, 0, 0, 0],
    [0, 0, 0, 0, 0, -1, 0, 0],
    [0, 0, 0, 0, 0, 0, -1, 0]];;

Tofolli := [
    [1, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 1],
    [0, 0, 0, 0, 0, 0, 1, 0]];;

Cliff3 := Group(sii, isi, iis, hii, ihi, iih, wii, icz, czi);; # Order 743178240
Pauli3 := Group(wii, xii, ixi, iix, zii, izi, iiz);; # Order 256

# Print(Order(Pauli3), "\n");;

# ca in Cliff3 = false
# ca in third level of clifford hierarchy = true

Cliff3_12 := Group(sii, isi, hii, ihi, wii, czi);;
Cliff3_13 := Group(sii, iis, hii, iih, wii);; # cz on 1&3 ??
Cliff3_23 := Group(isi, iis, ihi, iih, wii, icz);;

#SmCliff3 := Group(sii, isi, iis, hii, ihi, iih, icz, czi);; # Order 743178240

Print("warming up...\n");
Order(Cliff3);; # need this line otherwise membership test eats all memory...!
Print("Ok\n");

#Print("Hadamard8 in Cliff3 ", Hadamard8 in Cliff3, "\n"); # true

in_first_level := function(U3)
    return U3 in Pauli3;
end;;

in_second_level := function(U3)
    if U3 in Cliff3_12 then return true; fi;
    if U3 in Cliff3_13 then return true; fi;
    if U3 in Cliff3_23 then return true; fi;
    if U3 in Cliff3 then return true; fi;
    return false;
end;;

#in_second_level := function(U3)
#    # Is U3 in the second level of the clifford hierarchy ?
#    local A;
#    for g in Pauli3 do
#        A := U3*g*Inverse(U3)*Inverse(g);
#        if in_first_level(A) then continue; fi;
#        return false; # no
#    od;
#    return true; # yes
#end;;

in_third_level := function(U3)
    # Is U3 in the third level of the clifford hierarchy ?
    local A;
    for g in Pauli3 do
        A := U3*g*Inverse(U3)*Inverse(g);
        if in_second_level(A) then continue; fi;
        return false; # no
    od;
    return true; # yes
end;;

#Print(in_third_level(ca1), "\n"); # true
#quit;

in_fourth_level := function(U3)
    # Is U3 in the fourth level of the clifford hierarchy ?
    local A;
    for g in Pauli3 do
        A := U3*g*Inverse(U3)*Inverse(g);
        if in_third_level(A) then continue; fi;
        return false; # no
    od;
    return true; # yes
end;;

in_fifth_level := function(U3)
    # Is U3 in the fifth level of the clifford hierarchy ?
    local A;
    for g in Pauli3 do
        A := U3*g*Inverse(U3)*Inverse(g);
        if in_fourth_level(A) then continue; fi;
        return false; # no
    od;
    return true; # yes
end;;

get_level := function(U3)
    if U3 in Pauli3 then return "1\c"; fi;
    if in_second_level(U3) then return "2\c"; fi;
    if in_third_level(U3) then return "3\c"; fi;
    if in_fourth_level(U3) then return "4\c"; fi;
    if in_first_level(U3) then return "5\c"; fi;
    return ".\c";
end;;

#Print("in_third_level(d):", in_third_level(cc), "\n"); # true
#Print("in_third_level(ca):", in_third_level(ca), "\n"); # true
#Print("in_third_level(cb):", in_third_level(cb), "\n"); # true
#Print("in_third_level(cc):", in_third_level(cc), "\n"); # true
#Print("in_third_level(Tofolli):", in_third_level(Tofolli), "\n"); # true
#Print("in_third_level(Tofolli*ca):", in_third_level(Tofolli*ca), "\n"); # false

U3 := [
    [1, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0, 0, 0, 1]];

Print("# Where do control control 1-qubit Pauli gates live ?\n");
for g in Pauli1 do
    #Print(g, "\n");
    U3{[7,8]}{[7,8]} := g;
    Print(get_level(U3));
od;
Print("\n");

Print("# Where do control control 1-qubit clifford gates live ?\n");
for g in Cliff1 do
    #Print(g, "\n");
    U3{[7,8]}{[7,8]} := g;
    Print(get_level(U3));
od;
Print("\n");

Print("# Where do control 2-qubit Pauli gates live ?\n");
for g in Pauli2 do
    #Print(g, "\n");
    U3{[5,6,7,8]}{[5,6,7,8]} := g;
    Print(get_level(U3));
od;
Print("\n");

Print("# Where do control 2-qubit clifford gates live ?\n");
for g in Cliff2 do
    #Print(g, "\n");
    U3{[5,6,7,8]}{[5,6,7,8]} := g;
    Print(get_level(U3));
od;

Print("\n");

Print("Done.\n");


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

#
# Functions
#
IsASPFreqBasis := x -> IsRec(x) and IsBound(x.isASPFreqBasis) and x.isASPFreqBasis;
IsASPTimeBasis := x -> IsRec(x) and IsBound(x.isASPTimeBasis) and x.isASPTimeBasis;

Class(BasisOps, PrintOps, rec(
    \= := (b1, b2) -> Cond(
        not IsASPFreqBasis(b1) or not IsASPFreqBasis(b2), false,
        ObjId(b1)=ObjId(b2) and b1.rChildren() = b2.rChildren())
));

#
# Base classes
#
Class(ASPTimeBasis, rec(
    isAtomic := true, # NOTE: these probably should not be atomic, maybe use TimeComp(Freq_E) for composed bases
    isASPTimeBasis := true,
    doNormalize := false,
    operations := PrintOps,
    print := self >> Print(self.__name__, When(self.doNormalize, ".norm()")),
    norm := self >> CopyFields(self, rec(doNormalize:=true)),
    rChildren := self >> [],
    from_rChildren := (self, rch) >> self,
    unparse := "ConstClass",
));

Class(ASPComposedTimeBasis, ASPTimeBasis, rec(
    composedFrom := self >> Error("must be defined in the subclass"),
    toX := (self, alg) >> Checked(ObjId(alg) = XN_skew, IsEvenInt(alg.n),
        let(C := When(self.doNormalize, self.composedFrom().norm(),
                                        self.composedFrom()),
            Tensor(C.to1X(XN_skew(2, alg.a, alg.rot)), I(alg.n/2))))
));

Class(ASPFreqBasis, rec(
    doNormalize := false,
    isASPFreqBasis := true,
    dim := self >> Error("must be defined in the subclass"),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), rch),
    unparse := "RewritableObjectExp",
));

Class(ASPFreqBasis1d,  ASPFreqBasis, rec(
    __call__ := (self, scale1d) >> WithBases(self, rec(
            scale1d := scale1d, operations := BasisOps
    )),
    rChildren := self >> [self.scale1d],
    rSetChild := rSetChildFields("scale1d"),
    print := self >> Print(self.name, "(", self.scale1d, ")", When(self.doNormalize, ".norm()")),
    dim := self >> 1,
    hashAs := self >> ObjId(self)(1)
));

Class(ASPFreqBasis1d2d,  ASPFreqBasis, rec(
    __call__ := (self, scale1d, scale2d) >> WithBases(self, rec(
            scale1d := scale1d, scale2d := scale2d, operations := BasisOps
    )),
    rChildren := self >> [self.scale1d, self.scale2d],
    rSetChild := rSetChildFields("scale1d", "scale2d"),
    print := self >> Print(self.name, "(", self.scale1d, ", ", self.scale2d, ")",
        When(self.doNormalize, ".norm()")),

    norm := self >> CopyFields(self, rec(doNormalize:=true)),

    dim := self >> 2,
    hashAs := self >> ObjId(self)(1, 1),

    to1X_2d0 := (self, rot) >> I(2),
    to1X_2d := (self, a, rot) >> Error("Must be implemented in a subclass"),
    to1X := (self, alg) >> let(t := ObjId(alg), s1 := self.scale1d, s2 := self.scale2d, Cond(
        alg.n=1, 1/s1 * I(1),

        alg.n=2 and (t in [XN_min_1, XN_min_1U]),
            1/s2 * self.to1X_2d0(alg.rot),

        alg.n=2 and (t in [XN_skew, XN_plus_1]), let(
            a := alg.a, #EvalScalar(alg.a),
            aa := Cond(self.doNormalize, cond(gt(a, 0.5), 1-a, a), a), #Cond(self.doNormalize and a > 1/2, 1-a, a),
            1/s2 * self.to1X_2d(aa, alg.rot)),

        Error("Algebra <alg> must have dimension of 1 or 2"))),

    from1X_2d0 := (self, rot) >> I(2),
    from1X_2d := (self, a, rot) >> Mat(MatSPL(self.to1X_2d(a, rot))^-1),
    from1X := (self, alg) >> let(t := ObjId(alg), s1 := self.scale1d, s2 := self.scale2d, Cond(
        alg.n=1, s1 * I(1),

        alg.n=2 and (t in [XN_min_1, XN_min_1U]),
            s2 * self.from1X_2d0(alg.rot),

        alg.n=2 and (t in [XN_skew, XN_plus_1]), let(
            a := alg.a, #EvalScalar(alg.a),
            aa := Cond(self.doNormalize, cond(gt(a, 0.5), 1-a, a), a),
            s2 * self.from1X_2d(aa, alg.rot)),

        Error("Algebra <alg> must have dimension of 1 or 2"))),
));

#
# TIME BASIS Classes
#
Declare(Freq_1, Freq_1H, Freq_T, Freq_S, Freq_TH, Freq_THU,  Freq_E, Freq_EH,  Freq_H, Freq_HH);

Declare(Time_TX, Time_SX, Time_THX, Time_THUX,  Time_EX, Time_EHX,  Time_HX, Time_HHX);

# ASP time basis: standard "time" basis (1, x, ..., x^{n-1})
Class(Time_TX,  ASPTimeBasis, rec(
    toX := (self, alg) >> I(alg.n)
));

# ASP time basis: symmetric "time" basis (-x^{n/2}, ..., 1, x, ..., x^{n/2-1})
Class(Time_SX,  ASPTimeBasis, rec(
    toX := (self, alg) >> let(t := ObjId(alg), hf := Int(alg.n/2),
        Cond(t in [XN_min_1, XN_min_1U],
                Z(alg.n, hf),
             t = XN_plus_1 or (t = XN_skew and alg.a = 1/4),
                 DirectSum(I(Int((alg.n+1)/2)), -I(hf)) * Z(alg.n, hf),
             t = XN_skew,
                 Tensor(Freq_S(1,1).to1X(XN_skew(2, alg.a, alg.rot)), I(alg.n/2)),
             Error("Unrecognized algebra <alg>")))
));

Class(Time_THX,  ASPComposedTimeBasis, rec(composedFrom := self >> Freq_TH(1,1)));
Class(Time_THUX, ASPComposedTimeBasis, rec(composedFrom := self >> Freq_THU(1,1)));
Class(Time_EX,   ASPComposedTimeBasis, rec(composedFrom := self >> Freq_E(1,1))); # e_a(x^n) (*) t_n
Class(Time_EHX,  ASPComposedTimeBasis, rec(composedFrom := self >> Freq_EH(1,1)));
Class(Time_HX,   ASPComposedTimeBasis, rec(composedFrom := self >> Freq_H(1,1))); # h_a(x^n) (*) t_n
Class(Time_HHX,  ASPComposedTimeBasis, rec(composedFrom := self >> Freq_HH(1,1)));

#
# FREQUENCY BASIS Classes
#

# ASP frequency basis: (1)
Class(Freq_1,  ASPFreqBasis1d, rec(
    timeBasis := self >> Time_TX
));

Class(Freq_1H,  ASPFreqBasis1d, rec(
    timeBasis := self >> Error("not implemented"),
    to1X := (self, alg) >> Error("not implemented"),
));

# ASP frequency basis: (1, x), also implies 2d spectral components, ie real DFT
Class(Freq_T, ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_TX,
    to1X := (self, alg) >>  let(t := ObjId(alg), s1 := self.scale1d, s2 := self.scale2d, Cond(
        alg.n = 2, 1/s2 * I(2),
        alg.n = 1, 1/s1 * I(1),
        Error("Algebra <alg> must have dimension of 1 or 2"))),
    from1X := (self, alg) >>  let(t := ObjId(alg), s1 := self.scale1d, s2 := self.scale2d, Cond(
        alg.n = 2, s2 * I(2),
        alg.n = 1, s1 * I(1),
        Error("Algebra <alg> must have dimension of 1 or 2"))),
));

# ASP frequency basis: (x^-1, 1), also implies 2d spectral components, ie real DFT
Class(Freq_S, ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_SX,
    to1X_2d0 := (self, rot) >> J(2),
    to1X_2d := (self, a, rot) >> Mat(_stripval([[ 2*cospi(2*a*rot), 1],
                                                [             -1  , 0]])),
));

# ASP frequency basis: (x^1/2, x^3/2) for type 2--4 transforms, also implies 2d spectral components, ie real DFT
Class(Freq_TH, ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_THX,
    to1X_2d := (self, a, rot) >> let(c := cospi(2*a*rot), q := 1/(2*cospi(a*rot)),
        Mat(_stripval(q * [[1+2*c, 1],
                           [-1,    1]])))
));

Class(Freq_THU, ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_THUX,
    to1X_2d := (self, a, rot) >> let(c := cospi(2*a*rot), q := 1/(2*cospi(a * rot)),
        Mat(_stripval(q^2 * [[1+2*c, 1],
                             [-1,    1]])))
));

# ASP frequency basis: (1, (x-cos(a)) / sin(a)), also implies 2d spectral components, ie real DFT
Class(Freq_E,  ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_EX,
    to1X_2d := (self, a, rot) >> let(c := cospi(2*rot*a), s := sinpi(2*rot*a),
        Mat(_stripval([[1, -c/s],
                       [0,  1/s]]))),
    from1X_2d := (self, a, rot) >> let(c := cospi(2*rot*a), s := sinpi(2*rot*a),
        Mat(_stripval([[1,  c],
                       [0,  s]])))
));

Class(Freq_EH,  ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_EX,
    to1X_2d := (self, a, rot) >> let(c := r->cospi(r*2*rot*a), s := r->sinpi(r*2*rot*a),
        Mat(_stripval(1/s(1) * [[ s(3/2), -c(3/2)],
                                [-s(1/2),  c(1/2)]])))
));

# ASP frequency basis: (1 + (x-cos(a)) / sin(a), 1 - (x-cos(a))/sin(a)), ie Hartley transform
Class(Freq_H,  ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_HX,
    to1X_2d := (self, a, rot) >> let(c := cospi(2*rot*a), s := sinpi(2*rot*a),
        Mat(_stripval(1/2 * [[1-c/s, 1+c/s],
                             [  1/s,  -1/s]])))
));

Class(Freq_HH,  ASPFreqBasis1d2d, rec(
    timeBasis := self >> Time_EX,
    to1X_2d := (self, a, rot) >> let(s := sinpi(2*rot*a),
        cas := r -> cospi(r*2*rot*a)+sinpi(r*2*rot*a), cms := r -> cospi(r*2*rot*a)-sinpi(r*2*rot*a),
        Mat(_stripval(1/(2*s) * [[-cms(3/2), -cas(3/2)],
                                 [ cms(1/2),  cas(1/2)]])))
));


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

#F ==========================================================================
#F Diag(<diag-func>)
#F ==========================================================================
Class(Diag, BaseMat, rec(
    _short_print := true,
    abbrevs := [ arg -> When(Length(arg)=1,
                         When(IsFunction(arg[1]) or IsFuncExp(arg[1]) or (IsRec(arg[1]) and not IsScalar(arg[1])) or IsList(arg[1]), [arg[1]], [[arg[1]]]),
                 [Flat(arg)]) ],

    new := meth(self, L)
        if IsList(L) then
            L := List(L, toExpArg);
            L := FList(UnifyTypes(List(L, x->x.t)), L);
        elif not IsRec(L) or not IsBound(L.lambda) then
            Error("List of numbers or LambdaList expected");
        fi;
        return SPL(WithBases(self, rec(element := L, TType := L.range()))).setDims();
    end,
    #-----------------------------------------------------------------------
    dims := self >> Replicate(2, self.element.domain()),
    #-----------------------------------------------------------------------
    isPermutation := self >> false,
    #-----------------------------------------------------------------------
    isReal := self >> let(
	t := self.element.range(),
        tt := Cond(IsVecT(t), t.t, t),
	tt <> TComplex and ObjId(tt)<>T_Complex),
    #-----------------------------------------------------------------------
    toAMat := self >> DiagonalAMat(List(EvalScalar(self.element.tolist()), EvalScalar)),
    #-----------------------------------------------------------------------
    transpose := self >> self,
    conjTranspose := self >> Diag(FConj(self.element)),
    inverse := self >> Diag(FInv(self.element)),
    #-----------------------------------------------------------------------
    normalizedArithCost := self >> 0,
    #-----------------------------------------------------------------------
    arithmeticCost := (self, costMul, costAddMul) >> Sum(self.element.tolist(), costMul),
    #-----------------------------------------------------------------------
    toloop := (self, bksize) >> let(
	j := Ind(self.element.domain()),
	ISum(j,
	    Scat(fTensor(fBase(j), fId(1))) *
	    Diag(fCompose(self.element, fTensor(fBase(j), fId(1)))) *
	    Gath(fTensor(fBase(j), fId(1)))
	).split(bksize)
    )

));

#F ==========================================================================
#F DiagCpxSplit(<diag-func>) - complex diagonal specified in split format
#F NOTE: maybe this should be somehow replaced by VRCDiag (?)
#F  this object is helpful for complex code, because it improves performance
#   when using SSE
#F ==========================================================================
Class(DiagCpxSplit, Diag, rec(
    #-----------------------------------------------------------------------
    dims := self >> Replicate(2, div(self.element.domain(), 2)),
    #-----------------------------------------------------------------------
    isReal := self >> false,
    #-----------------------------------------------------------------------
    toAMat := meth(self)
        local lst, i, cpxlst;
        lst := List(EvalScalar(self.element.tolist()), EvalScalar);
        cpxlst := [];
        for i in [0..Length(lst)/2-1] do
            Add(cpxlst, lst[2*i+1]+E(4)*lst[2*i+2]);
        od;
        return DiagonalAMat(cpxlst);
    end,
    #-----------------------------------------------------------------------
    transpose := self >> self,
    conjTranspose := self >> ObjId(self)(FRConj(self.element)),
    #-----------------------------------------------------------------------
    normalizedArithCost := self >> 0,
    #-----------------------------------------------------------------------
    #arithmeticCost := (self, costMul, costAddMul) >> Sum(self.element.tolist(), costMul)
));

Declare(RowVec, ColVec);

Class(RowVec, Diag, rec(
    dims := self >> [1, self.element.domain()],
    toAMat := self >> AMatMat([List(self.element.tolist(), EvalScalar)]),
    transpose := self >> ColVec(self.element),
    conjTranspose := self >> ColVec(FConj(self.element)),
    arithmeticCost := (self, costMul, costAddMul) >> Sum(self.element.tolist(), costAddMul),

    toBlk := self >> Checked(not IsSymbolic(self.element.domain()), Blk([self.element.tolist()])),
    toDiagBlk := self >> Checked(not IsSymbolic(self.element.domain()),
	Blk([Replicate(EvalScalar(self.element.domain()), 1)]) * Diag(self.element)),
));

Class(ColVec, Diag, rec(
    dims := self >> [self.element.domain(), 1],
    toAMat := self >> AMatMat(TransposedMat([List(self.element.tolist(), EvalScalar)])),
    transpose := self >> RowVec(self.element),
    conjTranspose := self >> RowVec(FConj(self.element)),

    toBlk := self >> self.transpose().toBlk().transpose(),
    toDiagBlk := self >> self.transpose().toDiagBlk().transpose(),
));

Declare(RCDiag);

#F RCDiagonalAMat(<elts>, <post>)
#F
#F Works similarly to RCDiag, but creates an AMat object instead of SPL.
#F See Doc(RCDiag).
#F
#F <elts> must be a list of size 2*n, which consists of interleaved real/imaginary data.
#F (not a symbolic function as in RCDiag)
#F
#F <post> must be an <amat>, and not spl
#F
RCDiagonalAMat := (elts, post) ->
    DirectSumAMat(List([1..Length(elts)/2], x ->
            let(r := EvalScalar(elts[2*x-1]), i := EvalScalar(elts[2*x]),
                post * AMatMat([[r,-i],[i,r]]))));

#F RCDiag(<diagfunc>)
#F RCDiag(<diagfunc>, <2x2 spl>)
#F
#F  Complex diagonal operating on real data in (r,i) format.
#F  Represents a block diagonal with 2x2 blocks (complex rotations),
#F  with post-processing matrix <post-spl> applied to each block.
#F
#F  For n complex numbers, <diagfunc> must be a diagonal function of size 2*n,
#F  which consists of interleaved real/imaginary data.
#F
#F  Example: RCDiag(RCData(Tw1(8,4,1))              normal twiddle diagonal
#F           RCDiag(RCData(Tw1(8,4,1)), 1/2*F(2))   twiddles for Hartley transform
#F
Class(RCDiag, BaseMat, rec(
    dims := self >> Replicate(2, self.element.domain()),
    area := self >> 2*self.element.domain(),
    transpose := self >> RCDiag(FRConj(self.element), self.post),

    conjTranspose := self >> Cond(self.element.range()=TComplex,
        RCDiag(FRConj(FConj(self.element)), self.post),
        RCDiag(FRConj(self.element), self.post)),

    toAMat := self >> let(elts := self.element.tolist(), # r,i,r,i,...
        Checked(Length(elts) mod 2 = 0,
            DirectSumAMat(List([1..Length(elts)/2], x ->
                    let(r := EvalScalar(elts[2*x-1]), i := EvalScalar(elts[2*x]),
                        self.post.toAMat() * AMatMat([[r,-i],[i,r]])))))),

    toloop := self >> let(
        func := self.element, # r,i,r,i,...
        j    := Ind(idiv(func.domain(),2)),
        re   := func.at(2*j),
        im   := func.at(2*j+1),
        IDirSum(j, j.range,
                self.post * Mat([[re, -im],
                                 [im,  re]]))),

    abbrevs := [
        () -> Error("Usage: \n  RCDiag(<diagfunc>, <2x2 spl>)\n",
                    "  RCDiag(<diagfunc>)\n",
                    "  RCDiag(<e0>, <e1>, ...)\n"),
        (L)    -> Checked(IsList(L) or IsFunction(L), [L, I(2)]),
        (L, P) -> Checked(IsList(L) or IsFunction(L), IsSPL(P), [L, P])
    ],

    new := meth(self, L, P)
        local inline, res;
        if IsList(L) then L := FList(L);
        elif not IsRec(L) or not IsBound(L.lambda) then
            Error("List of numbers or LambdaList expected");
        fi;
        res := SPL(WithBases(self, rec(element := L, post := P, TType := L.range())));
        res.dimensions := res.dims();
        return res;
    end,

    isReal := self >> self.element.range() <> TComplex and self.post.isReal(),
    rChildren := self >> [self.element, self.post],
    rSetChild := rSetChildFields("element", "post"),

    normalizedArithCost := self >> 0,
));

# Won't work for now because divisions by zero need to be handled.
# Declare(RCDiagLiftingSteps);
#
# #F RCDiagLiftingSteps(<diagfunc>)
# #F RCDiagLiftingSteps(<diagfunc>, <2x2 spl>)
# #F
# #F  Complex diagonal operating on real data in (r,i) format.
# #F  Represents a block diagonal with 2x2 blocks (complex rotations),
# #F  with post-processing matrix <post-spl> applied to each block.
# #F
# #F  Example: RCDiagLiftingSteps(T(8,4))   normal twiddle diagonal
# #F           RCDiagLiftingSteps(T(8,4), 1/2*F(2))   twiddles for Hartley transform
# #F
#
# Class(RCDiagLiftingSteps, BaseMat, rec(
#     dims := self >> Replicate(2, self.element.domain()),
#
#     transpose := self >> RCDiagLiftingSteps(FRConj(self.element), self.post),
#     conjTranspose := self >> self.transpose(),
#
#     toAMat := self >> let(elts := self.element.tolist(), # r,i,r,i,...
#   Checked(Length(elts) mod 2 = 0,
#       DirectSumAMat(List([1..Length(elts)/2],
#       x -> let(r := EvalScalar(elts[2*x-1]), i := EvalScalar(elts[2*x]),
#           self.post.toAMat() * AMatMat([[r,-i],[i,r]])))))),
#
#     abbrevs := [
#   () -> Error("Usage: \n  RCDiagLiftingSteps(<diagfunc>, <2x2 spl>)\n",
#          "  RCDiagLiftingSteps(<diagfunc>)\n",
#          "  RCDiagLiftingSteps(<e0>, <e1>, ...)\n"),
#   (L)    -> Checked(IsList(L) or IsFunction(L), [L, I(2)]),
#   (L, P) -> Checked(IsList(L) or IsFunction(L), IsSPL(P), [L, P])
#     ],
#
#     new := meth(self, L, P)
#         local inline, res;
#         if IsList(L) then L := FList(L);
#   elif not IsRec(L) or not IsBound(L.lambda) then
#       Error("List of numbers or LambdaList expected");
#   fi;
#   inline := IsBound(L.inline) and L.inline;
#         res := SPL(WithBases(self, rec(element := L, post := P, inline := inline)));
#   res.dimensions := res.dims();
#   return res;
#     end,
#
#     isReal := True,
#     rChildren := self >> [self.element, self.post],
#     rSetChild := rSetChildFields("element", "post"),
#     print := (self, i, is) >> Print(self.name, "(", self.element,
#         When(ObjId(self.post)<>I, Print(", ", self.post)), ")")
# ));


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

#F HashIndex and HashEqual Functions
#F ---------------------------------
#F

#F HashLookup( hashTable, key )
#F    returns the data associated with key in hashTable.
#F    returns false if key is not found in hashTable.
#F
HashLookup := function( hashTable, key )
   local index, entry, pos, chash, temp;

   if not IsHashTable( hashTable ) then
      Error( "<hashTable> is not a valid HashTable" );
   fi;

   index := hashTable.indexFun( key, hashTable.size );

   if not ( index > 0 and index <= hashTable.size ) then
      Error( "Did not obtain a valid hash index for key" );
   fi;

   if IsBound( hashTable.entries[index] ) then
      entry := hashTable.entries[index];
      pos := PositionProperty( entry, a -> hashTable.equalFun(a.key, key) );

      if pos = false then
	  return false;
      else
 #      # catch the case of hash-identical but not identical spls
#       elif IsSPL(key) and not IsIdenticalSPL(entry[pos].key, key) then
#             chash := Copy(entry[pos].data);
#             if(IsList(chash)) then
#                for index in [1..Length(chash)] do
#                   chash[index].ruletree :=
# 		  # NOTE:
#                   spiral.formgen.ApplyRuleTreeSPL(entry[pos].data[index].ruletree, key);
#                od;
#             else
# 		# NOTE:
#                chash.ruletree :=
#                spiral.formgen.ApplyRuleTreeSPL(entry[pos].data.ruletree, key);
#             fi;
#             return chash;
#       else
      return entry[pos].data;
      fi;
   else
      return false;
   fi;
end;

MultiHashLookup := function(hashes, key)
    local h, lkup;
    for h in hashes do
        lkup := HashLookup(h, key);
	if lkup<>false then return lkup; fi;
    od;
    return false;
end;

Declare(HashIndexSPLRec);

HashIndexParams := function(param, index)
     local p;
     # hash symbolic Value(type, val) simply as <val>
     if IsValue(param) then param := param.v; fi;

     if IsFFE(param) then
     index := index + 1 + When(param <> GF(2).zero, 0, 1);
     elif IsInt(param) then
	 index := index * (param+1);
     elif IsString(param) then
	 index := index * (Length(param)+1);

     elif IsFloat(param) then
	 index := index + 1;
     elif IsRat(param) then
	 index := index + 1;
     elif IsFloat(param) then
	 index := index + 1;
      ## NOTE: How to handle bools???
     elif IsBool(param) then
	 index := index * When(param, 2,3);

     elif IsCyc(param) then
	 index := index + 1;
     elif IsList(param) then
	 for p in param do
	     index := HashIndexParams(p, index);
	 od;
     elif IsMat(param) then
	 index := index * Product(DimensionsMat(param));
     elif IsPolynomial(param) then
	 index := index * LaurentDegree(param);

      # MP: now we have transforms as parameter and thus need to handle this case
      # by calling the function recursively
     elif IsSPL(param) then
	 index := index + HashIndexSPLRec(param);

     elif IsClass(ObjId(param)) then
         index := index + BagAddr(ObjId(param));
     fi;
     return index;
end;

#F HashIndexSPLRec( <spl> )
#F   Does the actual dirty work for HashIndexSPL recursively
#F   Do not call directly
#F

HashIndexSPLRec := function( spl )
   local type, index, param, params;

   if not IsSPL(spl) then
      Error( "<spl> must be a valid SPL" );
   fi;

   if IsNonTerminal(spl) and IsBound(spl.params) then
      index := BagAddr(ObjId(spl));
      params := When(IsBound(spl.HashId), spl.HashId(), spl.params);
      if IsInt( params ) then
	  index := index * (params+1);
      else
	  params := When(not IsList(params), [params], params);
	  for param in params do
	      index := HashIndexParams(param, index);
	  od;
      fi;
   elif IsBound(spl.children) then
       index := Product( spl.children(), a->HashIndexSPLRec(a) );
   elif IsBound(spl.element) and IsList(spl.element) then
       index := Length(spl.element);
   else
       index := BagAddr(ObjId(spl));
   fi;

   return index;
end;

#F HashIndexSPL( <spl>, <hashSize> )
#F    one possible method of returning a hash index (probably not that good).
#F    takes a SPL and returns a hash index.
#F
HashIndexSPL := function( spl, hashSize )
   if not IsSPL(spl) then
      Error( "<spl> must be a valid SPL" );
   fi;

   if IsBound(spl.hashIndex) then
      return ( (spl.hashIndex mod hashSize) + 1 );
   fi;

   spl.hashIndex := HashIndexSPLRec(spl);
   return ( (spl.hashIndex mod hashSize) + 1 );
end;

#F HashIndexRuleTree( <ruletree>, <hashSize> )
#F    one possible method of returning a hash index (probably not that good).
#F    takes a RuleTree and returns a hash index.
#F

HashIndexRuleTree := function( tree, hashSize )
   local index,
         child;

   index := BagAddr(tree.rule);
   if tree.transposed then
      index := index*3;
   else
      index := index*7;
   fi;
   if IsBound(tree.splOptions) and tree.splOptions="unrolled" then
      index := index*11;
   else
      index := index*13;
   fi;
   index := ((index+1) * (HashIndexSPL(tree.node, hashSize)+1))
            mod hashSize;
   for child in tree.children do
      index := ((index+1) * (HashIndexRuleTree(child, hashSize)+1))
               mod hashSize;
   od;
   return index+1;
end;

#F HashTableSPL() - create hashtable with SPLs as keys
HashTableSPL := function()
   return HashTable( HashIndexSPL, IsHashIdenticalSPL, "HashTableSPL()" );
end;


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

#############################################################################
##
##  AG_InverseLessThanForLetters(<w1>, <w2>)
##
##  Compares w1 and w2 according to lexicografic ordering given by
##  x1 < x1^-1 < x2 < x2^-1 < ...
##
BindGlobal("AG_InverseLessThanForLetters",
function(w1, w2)
  local i, er1, er2;

  if Length(w1) <> Length(w2) then
    return Length(w1) < Length(w2);
  fi;

  er1 := LetterRepAssocWord(w1);
  er2 := LetterRepAssocWord(w2);
  for i in [1..Length(er1)] do
    if AbsInt(er1[i]) <> AbsInt(er2[i]) then
      return AbsInt(er1[i]) < AbsInt(er2[i]);
    fi;
    if er1[i] <> er2[i] then
      return er1[i] > er2[i];
    fi;
  od;

  return false;
end);

#############################################################################
##
##  AG_ReducedListOfWordsByNielsen(<words_list>)
##  AG_ReducedListOfWordsByNielsenBack(<words_list>)
##  AG_ReducedListOfWordsByNielsen(<words_list>, <string>)
##  AG_ReducedListOfWordsByNielsenBack(<words_list>, <string>)
##
InstallMethod(AG_ReducedListOfWordsByNielsen, [IsAssocWordCollection],
function(words)
  return AG_ReducedListOfWordsByNielsen(words, \<);
end);

InstallMethod(AG_ReducedListOfWordsByNielsenBack, [IsAssocWordCollection],
function(words)
  return AG_ReducedListOfWordsByNielsen(words, \<);
end);

InstallMethod(AG_ReducedListOfWordsByNielsen, [IsAssocWordCollection, IsString],
function(words, string)
  return AG_ReducedListOfWordsByNielsen(words, AG_InverseLessThanForLetters);
end);

InstallMethod(AG_ReducedListOfWordsByNielsenBack, [IsAssocWordCollection, IsString],
function(words, string)
  return AG_ReducedListOfWordsByNielsen(words, AG_InverseLessThanForLetters);
end);

#############################################################################
##
#M  AG_ReducedListOfWordsByNielsen(<words_list>, <lt>)
##
InstallMethod( AG_ReducedListOfWordsByNielsen,
                    [IsAssocWordCollection, IsFunction],
function(words_list, lt)
  local result, transform, did_something, n, i, j, try_again, tmp;

  n := Length(words_list);
  result := ShallowCopy(words_list);
  transform := ShallowCopy(FreeGeneratorsOfFpGroup(FreeGroup(n)));
  did_something := false;
  try_again := true;

  for i in [1..n] do
    if not IsAssocWord(result[i]) then
      Print("error in AG_ReducedListOfWordsByNielsen(IsAssocWordCollection, IsFunction):\n");
      Print("  ", i, "-th element of list is not an associative word\n");
      return fail;
    fi;
  od;

  while try_again do
    try_again := false;

    for i in [1..n] do
    for j in [1..n] do

      if i = j then
        if lt(result[i]^-1, result[i]) then
          result[i] := result[i]^-1;
          transform[i] := transform[i]^-1;
          did_something := true;
          try_again := true;
        fi;
        continue;
      fi;

      if i > j and lt(result[i], result[j]) then
        tmp := result[i];
        result[i] := result[j];
        result[j] := tmp;
        tmp := transform[i];
        transform[i] := transform[j];
        transform[j] := tmp;
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]*result[j], result[i]) then
        result[i] := result[i]*result[j];
        transform[i] := transform[i]*transform[j];
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]*result[j], result[j]) then
        result[j] := result[i]*result[j];
        transform[j] := transform[i]*transform[j];
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]^-1*result[j], result[i]) then
        result[i] := result[i]^-1*result[j];
        transform[i] := transform[i]^-1*transform[j];
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]^-1*result[j], result[j]) then
        result[j] := result[i]^-1*result[j];
        transform[j] := transform[i]^-1*transform[j];
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]*result[j]^-1, result[i]) then
        result[i] := result[i]*result[j]^-1;
        transform[i] := transform[i]*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]*result[j]^-1, result[j]) then
        result[j] := result[i]*result[j]^-1;
        transform[j] := transform[i]*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]^-1*result[j]^-1, result[i]) then
        result[i] := result[i]^-1*result[j]^-1;
        transform[i] := transform[i]^-1*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]^-1*result[j]^-1, result[j]) then
        result[j] := result[i]^-1*result[j]^-1;
        transform[j] := transform[i]^-1*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;
    od;
    od;
  od;

  return [result, transform, did_something];
end);

#############################################################################
##
#M  AG_ReducedListOfWordsByNielsenBack(<words_list>, <lt>)
##
InstallMethod(AG_ReducedListOfWordsByNielsenBack,
                   [IsAssocWordCollection, IsFunction],
function(words_list, lt)
  local result, transform, did_something, n, i, j, try_again, tmp;

  n := Length(words_list);
  result := ShallowCopy(words_list);
  transform := ShallowCopy(FreeGeneratorsOfFpGroup(FreeGroup(n)));
  did_something := false;
  try_again := true;

  for i in [1..n] do
    if not IsAssocWord(result[i]) then
      Print("error in AG_ReducedListOfWordsByNielsenBack(IsAssocWordCollection, IsFunction):\n");
      Print("  ", i, "-th element of list is not an associative word\n");
      return fail;
    fi;
  od;

  while try_again do
    try_again := false;

    for i in [1..n] do
    for j in [1..n] do

      if i = j then
        if lt(result[i]^-1, result[i]) then
          result[i] := result[i]^-1;
          transform[i] := transform[i]^-1;
          did_something := true;
          try_again := true;
        fi;
        continue;
      fi;

      if i > j and lt(result[i], result[j]) then
        tmp := result[i];
        result[i] := result[j];
        result[j] := tmp;
        tmp := transform[i];
        transform[i] := transform[j];
        transform[j] := tmp;
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]^-1*result[j]^-1, result[j]) then
        result[j] := result[i]^-1*result[j]^-1;
        transform[j] := transform[i]^-1*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]^-1*result[j]^-1, result[i]) then
        result[i] := result[i]^-1*result[j]^-1;
        transform[i] := transform[i]^-1*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]*result[j]^-1, result[j]) then
        result[j] := result[i]*result[j]^-1;
        transform[j] := transform[i]*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]*result[j]^-1, result[i]) then
        result[i] := result[i]*result[j]^-1;
        transform[i] := transform[i]*transform[j]^-1;
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]^-1*result[j], result[i]) then
        result[i] := result[i]^-1*result[j];
        transform[i] := transform[i]^-1*transform[j];
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]^-1*result[j], result[j]) then
        result[j] := result[i]^-1*result[j];
        transform[j] := transform[i]^-1*transform[j];
        did_something := true;
        try_again := true;
      fi;

      if lt(result[i]*result[j], result[j]) then
        result[j] := result[i]*result[j];
        transform[j] := transform[i]*transform[j];
        did_something := true;
        try_again := true;
      fi;
      if lt(result[i]*result[j], result[i]) then
        result[i] := result[i]*result[j];
        transform[i] := transform[i]*transform[j];
        did_something := true;
        try_again := true;
      fi;

    od;
    od;

  od;

  return [result, transform, did_something];
end);

#############################################################################
##
##  AG_ComputeMihailovaSystemPairs(<pairs_list>)
##
InstallGlobalFunction(AG_ComputeMihailovaSystemPairs,
function(pairs)
  local result, i, nie, m, n, w, tmp,
        did_smth, npairs, transform,
        generate_full_group, nielsen_mihaylov, nielsen_low, rank,
        number_of_letters;

  if not IsDenseList(pairs) then
    Print("error in AG_ComputeMihailovaSystemPairs:  \n");
    Print("  argument is not an IsDenseList\n");
    return fail;
  fi;
  if not IsList(pairs[1]) then
    Print("error in AG_ComputeMihailovaSystemPairs:  \n");
    Print("  first element of list is not an IsList\n");
    return fail;
  fi;
  if Length(pairs[1]) <> 2 then
    Print("error in AG_ComputeMihailovaSystemPairs:  \n");
    Print("  can work only with pairs\n");
    return fail;
  fi;
  if not IsAssocWord(pairs[1][1]) then
    Print("error in AG_ComputeMihailovaSystemPairs:  \n");
    Print("  <arg>[1][1] is not IsAssocWord\n");
    return fail;
  fi;

  #############################################################################
  ##
  ##  generate_full_group
  ##
  generate_full_group := function(list, rank)
    local nie, i;
    nie := AG_ReducedListOfWordsByNielsen(list)[1];
    if Length(Difference(nie, [One(nie[1])])) <> rank then
      return false;
    fi;
    for i in [1..Length(nie)] do
      if Length(nie[i]) > 1 then return false; fi;
    od;
    return true;
  end;

  #############################################################################
  ##
  ##  rank
  ##
  rank := function(words)
    return Length(Difference(AG_ReducedListOfWordsByNielsen(words)[1],
                             [One(words[1])]));
  end;

  #############################################################################
  ##
  ##  number_of_letters
  ##
  number_of_letters := function(list)
    local letters, i, j;
    letters := [];
    for i in [1..Length(list)] do
      for j in [1..NumberSyllables(list[i])] do
        AddSet(letters, GeneratorSyllable(list[i], j));
      od;
    od;
    return Length(letters);
  end;

  #############################################################################
  ##
  ##  nielsen_mihaylov
  ##
  nielsen_mihaylov := function(words_list, m, n)
    local result, transform, did_something, try_again, nie, i, j, tf, pair,
          good_tf, good_pair, tmp;

    result := StructuralCopy(words_list);
    transform := ShallowCopy(FreeGeneratorsOfFpGroup(FreeGroup(m+n)));
    did_something := false;
    try_again := true;

    while try_again do
      try_again := false;

      nie := nielsen_low(result, m, n, \<);
      if nie[3] then
        did_something := true;
        try_again := true;
        result := nie[1];
        transform := AG_CalculateWords(nie[2], transform);
      fi;

      nie := AG_ReducedListOfWordsByNielsen(result{[m+1..m+n]});
      if nie[3] then
        did_something := true;
        try_again := true;
        result := Concatenation(result{[1..m]}, nie[1]);
        transform := Concatenation( transform{[1..m]},
                                    AG_CalculateWords(nie[2],
                                    transform{[m+1..m+n]}));
      fi;

      if rank(result{[m+1..m+n]}) = n then
        if List(result{[m+1..m+n]}, w -> Length(w)) = List([1..n], i -> 1) then
          ## ok
          try_again := false;
        else
          ##  try to minimize sum of lengths
          good_pair := false;
          for pair in ListX([m+1..m+n], [1..m], function(i,j) return [i,j]; end) do
            good_tf := false;
            for tf in [  [1,1,2,1],[2,1,1,1],[1,-1,2,1],[2,-1,1,1],
                        [1,1,2,-1],[2,1,1,-1],[1,-1,2,-1],[2,-1,1,-1]  ] do
              tmp := StructuralCopy(result);
              tmp[pair[1]] := tmp[pair[tf[1]]]^tf[2] * tmp[pair[tf[3]]]^tf[4];
              if  rank(tmp{[m+1..m+n]}) = n and
                  number_of_letters(tmp{[m+1..m+n]}) =
                    number_of_letters(result{[m+1..m+n]}) and
                  Sum(List(tmp{[m+1..m+n]}, w -> Length(w))) <
                    Sum(List(result{[m+1..m+n]}, w -> Length(w)))
              then
                good_tf := true;
                break;
              fi;
            od;
            if good_tf then
              good_pair := true;
              break;
            fi;
          od;
          if not good_pair then
            ##  give up
            return [result, transform, did_something];
          else
            result[pair[1]] :=  result[pair[tf[1]]]^tf[2] *
                                result[pair[tf[3]]]^tf[4];
            transform[pair[1]] := transform[pair[tf[1]]]^tf[2] *
                                  transform[pair[tf[3]]]^tf[4];
            try_again := true;
            did_something := true;
          fi;
        fi;
      else
        ##  try to make rank bigger
        for i in [1..m] do
          good_tf := false;
          pair := [m+1, i];
          for tf in [  [1,1,2,1],[2,1,1,1],[1,-1,2,1],[2,-1,1,1],
                      [1,1,2,-1],[2,1,1,-1],[1,-1,2,-1],[2,-1,1,-1]  ] do
            tmp := StructuralCopy(result);
            tmp[pair[1]] := tmp[pair[tf[1]]]^tf[2] * tmp[pair[tf[3]]]^tf[4];
            if   rank(tmp{[m+1..m+n]}) > rank(result{[m+1..m+n]}) and
                number_of_letters(tmp{[m+1..m+n]}) >=
                  number_of_letters(result{[m+1..m+n]})
            then
              good_tf := true;
              break;
            fi;
          od;
          if good_tf then
            good_pair := true;
            break;
          fi;
        od;
        if not good_pair then
          ##  give up
          return [result, transform, did_something];
        else
          result[pair[1]] :=  result[pair[tf[1]]]^tf[2] *
                              result[pair[tf[3]]]^tf[4];
          transform[pair[1]] := transform[pair[tf[1]]]^tf[2] *
                                transform[pair[tf[3]]]^tf[4];
          try_again := true;
          did_something := true;
        fi;
      fi;

    od;

    return [result, transform, did_something];
  end;

  #############################################################################
  ##
  ##  nielsen_low
  ##
  nielsen_low := function(words_list, m, n, lt)
    local result, transform, did_something, i, j, try_again, tmp, nie;

    result := ShallowCopy(words_list);
    transform := ShallowCopy(FreeGeneratorsOfFpGroup(FreeGroup(m+n)));
    did_something := false;
    try_again := true;

    while try_again do
      try_again := false;

      nie := AG_ReducedListOfWordsByNielsen(result{[1..m]});
      if nie[3] then
        result := Concatenation(AG_CalculateWords(nie[2], result{[1..m]}),
                                result{[m+1..m+n]});
        transform := Concatenation( AG_CalculateWords(nie[2], transform{[1..m]}),
                                    transform{[m+1..m+n]} );
        did_something := true;
        try_again := true;
      fi;

      for i in [1..m] do
        for j in [m+1..m+n] do
          if lt(result[i]^result[j], result[i]) then
            result[i] := result[i]^result[j];
            transform[i] := transform[i]^transform[j];
            did_something := true;
            try_again := true;
          fi;
          if lt(result[i]^(result[j]^-1), result[i]) then
            result[i] := result[i]^(result[j]^-1);
            transform[i] := transform[i]^(transform[j]^-1);
            did_something := true;
            try_again := true;
          fi;
          if lt((result[i]^-1)^result[j], result[i]) then
            result[i] := (result[i]^-1)^result[j];
            transform[i] := (transform[i]^-1)^transform[j];
            did_something := true;
            try_again := true;
          fi;
          if lt((result[i]^-1)^(result[j]^-1), result[i]) then
            result[i] := (result[i]^-1)^(result[j]^-1);
            transform[i] := (transform[i]^-1)^(transform[j]^-1);
            did_something := true;
            try_again := true;
          fi;
        od;
      od;
    od;

    return [result, transform, did_something];
  end;

  #############################################################################
  ##
  ##  MihailovaSystem body
  ##
  n := Length(FreeGeneratorsOfWholeGroup(Group(pairs[1][1])));
  m := Length(pairs) - n;
  npairs := StructuralCopy(pairs);
  transform := StructuralCopy(FreeGeneratorsOfFpGroup(FreeGroup(n+m)));
  did_smth := false;
  if not generate_full_group(List(pairs, p -> p[1]), n)
      or not generate_full_group(List(pairs, p -> p[2]), n)
  then
    Print("error in AG_ComputeMihailovaSystemPairs:  \n");
    Print("  projections do not generate full free group\n");
    return fail;
  fi;

  ##  if rank equals number of pairs then just make one coordinate nicer
  if m = 0 then
    nie := AG_ReducedListOfWordsByNielsen(List(npairs, p -> p[1]), "r");
    if nie[3] then
      tmp := List(npairs, p -> []);
      for i in [1..m+n] do
        tmp[i][1] := AG_CalculateWord(nie[2][i], List(npairs, p -> p[1]));
        tmp[i][2] := AG_CalculateWord(nie[2][i], List(npairs, p -> p[2]));
      od;
      npairs := StructuralCopy(tmp);
      transform := StructuralCopy(nie[2]);
      did_smth := true;
    fi;
    return [npairs, transform, did_smth];
  fi;

  ##  else try to do as much as possible

  ##  1. Apply Nielsen to first coordinate
  nie := AG_ReducedListOfWordsByNielsen(List(npairs, p -> p[1]), "r");
  if nie[3] then
    tmp := StructuralCopy(npairs);
    for i in [1..m+n] do
      tmp[i][1] := AG_CalculateWord(nie[2][i], List(npairs, p -> p[1]));
      tmp[i][2] := AG_CalculateWord(nie[2][i], List(npairs, p -> p[2]));
    od;
    npairs := StructuralCopy(tmp);
    transform := StructuralCopy(nie[2]);
    did_smth := true;
  fi;

  ##  2. Now apply nielsen_mihaylov to the second coordinate
  nie := nielsen_mihaylov(List(npairs, p -> p[2]), m, n);
  if nie[3] then
    tmp := StructuralCopy(npairs);
    for i in [1..m+n] do
      tmp[i][1] := AG_CalculateWord(nie[2][i], List(npairs, p -> p[1]));
      tmp[i][2] := AG_CalculateWord(nie[2][i], List(npairs, p -> p[2]));
    od;
    npairs := StructuralCopy(tmp);
    tmp := StructuralCopy(transform);
    for i in [1..m+n] do
      tmp[i] := AG_CalculateWord(nie[2][i], transform);
    od;
    transform := StructuralCopy(tmp);
    did_smth := true;
  fi;

  ##  3. Try to get nice generators on first coordinate
  nie := AG_ReducedListOfWordsByNielsenBack(List(npairs{[m+1..m+n]}, p -> p[1]), "r");
  if nie[3] then
    tmp := StructuralCopy(npairs);
    for i in [1..n] do
      tmp[m+i][1] := AG_CalculateWord(nie[2][i], List(npairs{[m+1..m+n]}, p -> p[1]));
      tmp[m+i][2] := AG_CalculateWord(nie[2][i], List(npairs{[m+1..m+n]}, p -> p[2]));
    od;
    npairs := StructuralCopy(tmp);
    tmp := StructuralCopy(transform);
    for i in [1..n] do
      tmp[m+i] := AG_CalculateWord(nie[2][i], transform{[m+1..m+n]});
    od;
    transform := StructuralCopy(tmp);
    did_smth := true;
  fi;

  return [npairs, transform, did_smth];
end);

#############################################################################
##
#M  AG_ReducedByNielsen(<words_list>)
##
InstallMethod(AG_ReducedByNielsen,
              "for [IsList and IsAssocWordCollection]",
              [IsList and IsAssocWordCollection],
function(words)
  if AG_Globals.use_inv_order_in_apply_nielsen then
    return AG_ReducedListOfWordsByNielsen(words, "back")[1];
  else
    return AG_ReducedListOfWordsByNielsen(words)[1];
  fi;
end);

#############################################################################
##
#M  AG_ReducedByNielsen(<autom_list>)
##
InstallMethod(AG_ReducedByNielsen,
              "for [IsList and IsAutomCollection]",
              [IsList and IsAutomCollection],
function(automs)
  local words;
  if IsEmpty(automs) then
    return [];
  fi;
  words := AG_ReducedByNielsen(List(automs, a -> a!.word));
  return List(words, w -> Autom(w, automs[1]));
end);

#E


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

Import(paradigms.common, paradigms.vector, paradigms.vector.sigmaspl);

_bDivisorPairs := N -> Concatenation(DivisorPairs(N), [[N, 1]]);

VBfly1 := VBase(F(2), 2);
VBfly3 := VBase(Mat([[1,E(4)],[1,-E(4)]]), 2);

Class(v_lo2, Exp);
Class(v_hi2, Exp);
Class(v_rev2, Exp);
Class(v_revhi2, Exp);
Class(v_neg01, Exp);
Class(v_neg03, Exp);
Class(v_neg23, Exp);
Class(v_neg2, Exp);
Class(v_mul1j, Exp);

VBaseHash := HashTableSPL();

VectorCodegen.VBase := (self, o, y, x, opts) >> let(
    lkup := HashLookup(VBaseHash, o),
    Cond(lkup <> false,
         lkup.code(o, y, x, opts),
         Error("VBase implementation of <o> not found in VBaseHash")));

# NOTE: increase vlen to 4 and RC the matrices
HashAdd(VBaseHash, VBase(L(4,2), 2),
    rec(
        spl := VBase(L(4,2), 2),
        code := (o, y, x, opts) -> chain(
            assign(vvref(y,0,2), v_lo2(vvref(x,0,2), vvref(x,1,2))),
            assign(vvref(y,1,2), v_hi2(vvref(x,0,2), vvref(x,1,2))))
    ));

HashAdd(VBaseHash, VBfly1,
    rec(
        spl := VBfly1,
        code := (o, y, x, opts) -> let(
            hi := TempVar(TVect(x.t.t, 2)),
            lo := TempVar(TVect(x.t.t, 2)),
            chain(
                assign(lo, v_lo2(vvref(x,0,2), vvref(x,0,2))),
                assign(hi, v_hi2(vvref(x,0,2), vvref(x,0,2))),
                assign(hi, v_neg03(hi)),
                assign(vvref(y, 0, 2), hi+lo)))
    ));

HashAdd(VBaseHash, VBfly3,
    rec(
        spl := VBfly3,
        code := (o, y, x, opts) -> let(
            hi := TempVar(TVect(x.t.t, 2)),
            lo := TempVar(TVect(x.t.t, 2)),
            chain(
                assign(lo, v_lo2(vvref(x,0,2), vvref(x,0,2))),
                assign(hi, v_revhi2(vvref(x,0,2), vvref(x,0,2))),
                assign(hi, v_neg03(hi)),
                assign(vvref(y, 0, 2), hi+lo)))
    ));

HashAdd(VBaseHash, VBase(J(2), 2),
    rec(
        spl := VBase(J(2), 2),
        code := (o, y, x, opts) ->
            assign(vvref(y, 0, 2), v_rev2(vvref(x,0,2)))
    ));

HashAdd(VBaseHash, VBase(I(2), 2),
    rec(
        spl := VBase(J(2), 2),
        code := (o, y, x, opts) ->
            assign(vvref(y, 0, 2), vvref(x,0,2))
    ));

# NOTE: Pull out twiddles, for 2-way nothing else needs to be done! (1-way cplx)
RulesFor(PSkewDFT3, rec(
   PSkewDFT3_toSkewDFT := rec(
       isApplicable := P -> IsEvenInt(P[1]),
       allChildren := P -> [[ SkewDFT(P[1]/2, P[2]) ]],
       rule := (P, C) -> let(n := P[1],
           M(n, n/2) *
           L(n,2) *
           Tensor(I(n/2), Mat([[1, E(4)], [1, -E(4)]])) *
           RC(C[1]) *
           L(n,n/2))
   )
));

RulesFor(BSkewDFT3, rec(
   BSkewDFT3_Base2 := rec(
       isApplicable := P -> P[1]=2,
       rule := (P, C) -> let(
       D := Dat1d(TReal, 2),
       re := nth(D, 0), im := nth(D, 1),
       Data(D, fPrecompute(FList(TReal, [cospi(2*P[2]), sinpi(2*P[2])])),
       F(2)*Mat([[1,re],
             [0,E(4)*im]])))
   ),

   BSkewDFT3_Base4 := rec(
       isApplicable := P -> P[1]=4,
       allChildren := P -> let(n:=P[1], a:=P[2], aa:=[a/2, (1-a)/2],
       [[ BSkewDFT3(n/2, aa[1]), BSkewDFT3(n/2, aa[2]), BRDFT3(4,a) ]]),
       rule := (P, C) ->
           K(P[1],P[1]/2) * DirectSum(C[1], C[2]) * C[3]
   ),

   BSkewDFT3_Fact := rec(
       switch := false,
       isApplicable := P -> P[1] = 4,
       allChildren := P -> let(n:=P[1], a:=P[2], aa:=[a/2, (1-a)/2],
       [[ BSkewDFT3(n/2, aa[1]), BSkewDFT3(n/2, aa[2]) ]]),
       rule := (P, C) -> K(P[1],P[1]/2) * DirectSum(C) * bruun2(P[1],P[2])
   ),

   BSkewDFT3_Decomp := rec(
       isApplicable := P -> P[1] > 4,
       allChildren := P -> let(N:=P[1], a:=P[2],
       Map2(DivisorPairs(N/2), (m,n) -> let(i := Ind(n),
           [BSkewDFT3(2*m, fr(2*n,i,2*a)), BRDFT3(2*n,a), InfoNt(i)]))),

       rule := (P, C, Nonterms) -> let(N := P[1], m := Cols(C[1])/2,
       K(N,2*m) * IterDirectSum(Nonterms[3].params[1], C[1]) * Tensor(C[2], I(m)))
   )
));

RulesFor(BSkewDFT4, rec(
   BSkewDFT4_Base2 := rec(
       isApplicable := P -> P[1]=2,
       rule := (P, C) -> let(w:=E(2*Denominator(P[2]))^Numerator(P[2]), cc:=Global.Conjugate,
       Mat([[w, w^3],
        [-cc(w), -cc(w)^3]]))
   ),

   BSkewDFT4_Base4 := rec(
       isApplicable := P -> P[1]=4,
       allChildren := P -> let(n:=P[1], a:=P[2], aa:=[a/2, (1-a)/2],
       [[ BSkewDFT4(n/2, aa[1]), BSkewDFT4(n/2, aa[2]), BRDFT3(4,a) ]]),
       rule := (P, C) ->
           K(P[1],P[1]/2) * DirectSum(C[1], C[2]) * C[3]
   ),

   BSkewDFT4_Decomp := rec(
       isApplicable := P -> P[1] > 4,
       allChildren := P -> let(N:=P[1], a:=P[2],
       Map2(DivisorPairs(N/2), (m,n) -> Concatenation(
           List([0..n-1], i->BSkewDFT4(2*m, fr(2*n,i,2*a).ev())),
           [BRDFT3(2*n,a)]))),
       rule := (P, C) -> let(N := P[1], m := Cols(C[1])/2,
       K(N,2*m) * DirectSum(DropLast(C,1)) * Tensor(Last(C), I(m)))
   )
));

RulesFor(BRDFT1, rec(
   BRDFT1_Base2 := rec(
       isApplicable := P -> P[1]=2,
       rule := (P, C) -> F(2)),

   BRDFT1_Base3 := rec(
       isApplicable := P -> P[1]=3,
       rule := (P, C) -> BRDFT1(P[1], P[2]).terminate()),

   BRDFT1_Decomp := rec(
       # NOTE: add support for odd sizes
       isApplicable := P -> P[1] > 2 and IsEvenInt(P[1]),
       allChildren := P -> let(N:=P[1],
       Map2(_bDivisorPairs(N/2), (m,n) -> let(i := Ind(n-1),
           When(n=1,
           [BRDFT1(m), BRDFT3(m), BRDFT1(2*n)],
           [BRDFT1(m), BRDFT3(m), BRDFT1(2*n), InfoNt(i), BRDFT3(2*m, (1+i)/(2*n))])))),

       rule := (P, C, Nonterms) -> let(N := P[1], m := Cols(C[1]), n := N/m/2,
       RC(Scat(IP(N/2, OS(n, -1)))) * RC(L(N/2, m)) * # RC(K'(N/2,m)) *
       DirectSum(RC(L(m, m/2)) * DirectSum(C[1], C[2]),
                 When(n=1, [], IterDirectSum(Nonterms[4].params[1], C[5]))) *
       Tensor(C[3], I(m)))
   )
));

# URDFT_Decomp:
# MatSPL(Kp(16,4))^-1 * MatSPL(DFT(16))*MatSPL(Tensor(URDFT(8), I(2)))^-
#
NewRulesFor(DFT, rec(
    DFT_Bruun_Decomp := rec(
        switch := false,
        # NOTE: add support for odd sizes
        # only apply if there are no tSPL tags.
        applicable := nt -> nt.params[1] > 2 and IsEvenInt(nt.params[1]) and not nt.hasTags(),

        children := nt -> let(
            N := nt.params[1],
            Map2(_bDivisorPairs(N/2), (m,n) -> let(
                i := Ind(n-1),
                When(n=1,
                    [DFT(m), BSkewDFT3(m), BRDFT1(2*n)],
                    [DFT(m), BSkewDFT3(m), BRDFT1(2*n), InfoNt(i), BSkewDFT3(2*m, (1+i)/(2*n))]
                )
            ))
        ),

        apply := (nt, C, nonterms) -> let(
            N := nt.params[1],
            m := Cols(C[1]),
            n := N/m/2,

            Scat(IP(N, OS(n, -1)))
            * L(N, 2*m)
#            * RC(K'(N/2,m))
            * DirectSum(
                L(2*m, m)
                * DirectSum(C[1], C[2]),
                When(n=1,
                    [],
                    IterDirectSum(Nonterms[4].params[1], C[5])
                )
            )
            * Tensor(C[3], I(m))
        )

#D       isApplicable := (self, P) >> P[1] > 2 and IsEvenInt(P[1]) and PHasNoTags(self.nonTerminal, P),
#D       allChildren := P -> let(N:=P[1],
#D       Map2(_bDivisorPairs(N/2), (m,n) -> let(i := Ind(n-1),
#D           When(n=1,
#D           [DFT(m), BSkewDFT3(m), BRDFT1(2*n)],
#D           [DFT(m), BSkewDFT3(m), BRDFT1(2*n), InfoNt(i), BSkewDFT3(2*m, (1+i)/(2*n))])))),
#D
#D       rule := (P, C, Nonterms) -> let(N := P[1], m := Cols(C[1]), n := N/m/2,
#D       Scat(IP(N, OS(n, -1))) * L(N, 2*m) * # RC(K'(N/2,m)) *
#D       DirectSum(L(2*m, m) * DirectSum(C[1], C[2]),
#D                 When(n=1, [], IterDirectSum(Nonterms[4].params[1], C[5]))) *
#D       Tensor(C[3], I(m)))
    )
));

Class(VWrapRC, VWrapBase, rec(
    __call__ := self >> self,
    wrap := (self, r, t, opts) >> @_Base(RC(t), r),
    twrap := (self, t, opts) >> RC(t)
));

NewRulesFor(DFT, rec(
   DFT_URDFT_Decomp := rec(
       switch := false,

       # NOTE: add support for odd sizes
       applicable := (self, t) >> let(n:=t.params[1],
          n > 4 and IsEvenInt(n) and not IsPrime(n/2)) and t.getTags() = [],

       maxRadix := 128,

       children := (self, t) >> let(N:=t.params[1], maxrad := self.maxRadix, rot := t.params[2],
               Map2(Filtered(DivisorPairs(N/2), x -> x[1] <= maxrad/2),
                   (m,n) -> [ DFT(2*m, rot), DFT(m, rot).addWrap(VWrapRC()),
                              GT(URDFT1(2*n, rot), GTVec, GTVec, [m]).withTags(t.getTags()) ])),

       apply := (t, C, Nonterms) -> let(
           N := t.params[1], m := Cols(C[2]), n := N/m/2, rot := t.params[2],
           j := Ind(n-1),

           Scat(Refl0_u(n, 2*m)) *
       DirectSum(
               BB(C[1]),
               IterDirectSum(j, BB(
                   #condM(2*m, m) *
                   L(2*m, 2) *
                   Tensor(I(m), Mat([[1, E(4)], [1, -E(4)]])) *
                   RC(C[2]) *
                   RC(DirectSum(I(1), Diag(fPrecompute(
                                   fCompose(Twid(N, m, rot, 0, 0, j+1), fAdd(m, m-1, 1)))))) *
                   L(2*m, m))
                   # can replace by jj * b * (rdft(a) x I2)
                   # b = b' * (I x L^4_2)
               )
           ) *
       C[3] #Tensor(C[3], I(m))
       )
   ),

   DFT_Base4_VecCx := rec(
       switch := false,
       requiredFirstTag := AVecRegCx,
       applicable := (self, t) >> t.params[1]=4 and t.firstTag().v = 2,
       apply := (t, C, Nonterms) ->
           VTensor(F(2), 2) *
           VDiag(Tw1(4,2,t.params[2]), 2) *
           VBase(L(4,2), 2) *
           VTensor(F(2), 2)
   ),

   DFT_URDFT_Decomp_VecCx := rec(
       switch := false,
       requiredFirstTag := AVecRegCx,

       # NOTE: add support for odd sizes
       applicable := (self, t) >> let(n:=t.params[1], k:=t.params[2],
           k = 1 and n > 4 and IsEvenInt(n)), # and t.getTags() = []),

       maxRadix := 32,

       children := (self, t) >> let(N:=t.params[1], maxrad := self.maxRadix, tags := t.getTags(),
               Map2(Filtered(DivisorPairs(N/2), x -> x[1] <= maxrad/2),
                   (m,n) -> let(j := Ind(n-1),
                       [ DFT(2*m).withTags(tags),
                         rDFT(2*m, (j+1)/(2*n), 1).withTags(tags),
                         GT(URDFT1(2*n), GTVec, GTVec, [m]).withTags(tags),
                         InfoNt(j) ]))),

       apply := (t, C, Nonterms) -> let(
           N := t.params[1], m := Cols(C[2])/2,
           j := Nonterms[4].params[1],
           v := t.getTags()[1].v,

           condKp(N, 2*m) *
       DirectSum(C[1],
               IDirSum(j, K(2*m, 2) * Tensor(I(m), VBfly3) * C[2])) *
       C[3]
       )
   )
));

RulesFor(PkRDFT1, rec(
    PkRDFT1_Base2 := rec(
    isApplicable := P -> P[1]=2,
    rule := (P, C) -> F(2)
    ),

    PkRDFT1_Bruun_Decomp := CopyFields(BRDFT1_Decomp, rec(
       allChildren := P -> let(N:=P[1],
       Map2(_bDivisorPairs(N/2), (m,n) -> let(i := Ind(n-1),
           When(n=1,
           [PkRDFT1(m), BSkewPRDFT(m), BRDFT1(2*n), InfoNt(i)],
           [PkRDFT1(m), BSkewPRDFT(m), BRDFT1(2*n), InfoNt(i), BSkewPRDFT(2*m, (1+i)/(2*n))])))),
       )),
));

RulesFor(BRDFT3, rec(
   BRDFT3_Base2 := rec(
       isApplicable := P -> P[1]=2,
       rule := (P, C) -> I(2)),

   BRDFT3_Base3 := rec(
       isApplicable := P -> P[1]=3,
       rule := (P, C) -> ApplyFunc(BRDFT3, P).terminate()),

   BRDFT3_Base4 := rec(
       isApplicable := P -> P[1]=4,
       rule := (P, C) -> let(
       a := P[2], D := Dat1d(TReal, 2), q := nth(D, 0), q2 := nth(D, 1),
       Data(D, fPrecompute(FList(TReal, [2*cospi(a), 4*cospi(a)^2 - 1])),
           L(4,2) * VStack(
           F(2)*Mat([[1, 0,-1, 0],
                 [0, 0, 0,-q]]),
           F(2)*Mat([[0, 1, 0,q2],
                 [0, 0, q, 0]]))))
   ),

   BRDFT3_Fact := rec(
       switch := false,
       isApplicable := P -> P[1] > 2,
       allChildren := P -> let(n:=P[1], a:=P[2], [[ BRDFT3(n/2, a/2), BRDFT3(n/2, (1-a)/2) ]]),
       rule := (P, C) -> K(P[1],P[1]/2) * DirectSum(C) * bruun2(P[1],P[2])
   ),

   BRDFT3_Trig := rec(
       switch := false,
       isApplicable := P -> P[1] > 2 and (P[1] mod 2) = 0,
       allChildren := P -> let(n:=P[1], a:=P[2], [[ PolyDTT(SkewDTT(DST3(n/2), 2*a)) ]]),
       rule := (P, C) -> let(n := P[1], nn := n/2-1,
       b := Diag(BHD(n/2, 1, -1)) *
            VStack(
           HStack(DirectSum(I(1), -I(nn-1)),  O(nn,1), -J(nn),   O(nn,1)),
           RowVec(Replicate(nn,0), -1, Replicate(nn+1, 0)),
           HStack(O(nn,1), I(nn),  O(nn,1), J(nn)),
           RowVec(Replicate(nn+1,0), 1, Replicate(nn, 0))),
       M(P[1],P[1]/2) * Tensor(C[1], I(2)) * b)
   ),

   BRDFT3_Decomp := rec(
       isApplicable := P -> P[1] > 4,
       allChildren := P -> let(N:=P[1], a:=P[2], rot := P[3],
       Map2(DivisorPairs(N/2), (m,n) -> let(i := Ind(n),
           [BRDFT3(2*m, fr(2*n,i,2*a), rot), BRDFT3(2*n,a,rot), InfoNt(i)]))),

       rule := (P, C, Nonterms) -> let(N := P[1], m := Cols(C[1])/2,
       RC(K(N/2,m)) * IterDirectSum(Nonterms[3].params[1], C[1]) * Tensor(C[2], I(m)))
   )
));

NewRulesFor(rDFT, rec(
    rDFT_Base4 := rec(
       requiredFirstTag := ANoTag,
       applicable := t -> t.params[1]=4,
       apply := (t, C, Nonterms) -> let(
       a := t.params[2],
           Diag(1,1,1,-1) *
           Tensor(F(2), I(2)) *
           DirectSum(I(2), RCDiag(fPrecompute(FList(TReal, [cospi(a), sinpi(a)])))) *
           L(4,2))
   ),

   rDFT_Base4_Vec := rec(
       requiredFirstTag := AVecRegCx,
       applicable := t -> t.params[1]=4 and t.firstTag().v = 2,
       apply := (t, C, Nonterms) -> let(
       a :=t.params[2], D := Dat1d(TReal, 2),
           VTensor(F(2), 2) *
           DirectSum(VBase(I(2), 2), VBase(J(2), 2)*VDiag(FList(TReal, [1,-1]), 2)) *
           VRCDiag(fPrecompute(VData(FList(TReal, [1, cospi(a), 0, -sinpi(a)]), 2)), 2)
       )  # for some reason its not 1 c 0 s ...
   ),

   rDFT_Decomp := rec(
       applicable := t -> t.params[1] > 4,
       children := t -> let(N := t.params[1], a := t.params[2], tags := t.getTags(),
       Map2(DivisorPairs(N/2), (m,n) -> let(i := Ind(n),
           [ rDFT(2*m, fr(2*n,i,2*a)).withTags(tags),
                 GT(rDFT(2*n, a), GTVec, GTVec, [m]).withTags(tags),
                 InfoNt(i)]))),

       apply := (t, C, Nonterms) -> let(N := t.params[1], m := Cols(C[1])/2,
       RC(K(N/2,m)) * IterDirectSum(Nonterms[3].params[1], C[1]) * C[2]),
   )
));
#s:=SPLRuleTree(ExpandSPL(rDFT(4).withTags([AVecRegCx(SSE_4x32f)]), SpiralDefaults)[2]);

# packed fmt
RulesFor(BSkewPRDFT, rec(
   BSkewPRDFT_Decomp := CopyFields(BRDFT3_Decomp, rec(
       allChildren := P -> let(N:=P[1], a:=P[2],
       Map2(DivisorPairs(N/2), (m,n) -> let(i := Ind(n),
           [BSkewPRDFT(2*m, fr(2*n,i,2*a)), BRDFT3(2*n,a), InfoNt(i) ]))),
   )),

   BSkewPRDFT_Base2 := rec(
       isApplicable := P -> P[1]=2,
       rule := (P, C) -> I(2)
   ),

   BSkewPRDFT_Base4 := rec(
       isApplicable := P -> P[1]=4,
       rule := (P, C) -> let(
       a := P[2], D := Dat1d(TReal, 6), d := Diag(1,-1),
       Data(D, fPrecompute(FList(TReal, [cospi(a), cospi(2*a), cospi(3*a),
                                             sinpi(a), sinpi(2*a), sinpi(3*a) ])),
           L(4,2) * VStack(
           F(2)*Mat([[1,        0,   nth(D,1),          0 ],
                 [0,  nth(D,0),         0,  nth(D, 2) ]]),
         d*F(2)*Mat([[0,        0,   nth(D,4),          0 ],
                 [0,  nth(D,3),         0,  nth(D, 5) ]]))))
   )
));

# algebraic fmt
RulesFor(BSkewRDFT, rec(
   BSkewRDFT_Decomp := rec(
       isApplicable := P -> P[1] > 4,
       allChildren := P -> let(N:=P[1], a:=P[2],
       Map2(DivisorPairs(N/2), (m,n) -> Concatenation(
           List([0..n-1], i->BSkewRDFT(2*m, fr(2*n,i,2*a).ev())),
           [BRDFT3(2*n,a)]))),
       rule := (P, C) -> let(N := P[1], m := Cols(C[1])/2,
       K(N,2*m) * DirectSum(DropLast(C,1)) * Tensor(Last(C), I(m)))
   )
));

RulesFor(DCT4, rec(
   DCT4_BSkew_Decomp := rec(
       switch := false,
       isApplicable := P -> P > 2,
       allChildren := P -> let(N:=P, a:=1/2,
       Map2(DivisorPairs(N), (m,n) -> Concatenation(
           List([0..n-1], i->SkewDTT(DCT4(m), fr(n,i,a).ev())),
           [BRDFT3(2*n,a/2)]))),

       rule := (P, C) -> let(N := P, m := Cols(C[1])/2, n:=Cols(Last(C))/2,
       i := Ind(m),
       K(N,2*m) *
       DirectSum(DropLast(C,1)) *
       K(N, 2*n) *
       ISum(i,
           Scat(H(N,2*n,(2*n)*i,1)) * Last(C) *
           DirectSum(I(n+1), -I(n-1)) * OS(2*n,-1) * Gath(BH(N,2*N-1,2*n,i,2*m))))
   )
));

# right := (m,n) -> let(N:=2*m*n, i:=Ind(m),
#     ISum(i,
#   Scat(H(N,2*n,(2*n)*i,1)) * BRDFT3(2*n,1/4) *
#   DirectSum(I(n), -I(n)) * Gath(BH1(2*N,2*n,i,2*m))));

# RC.toAMat := self >> AMatMat(RCMatCyc(MatSPL(self.child(1))));

# bb:=(n,a)->MatSPL(BRDFT3(n,a));;
# ts:=TensorProductMat;;
# ii:=IdentityMat;;
# ms:=MatSPL;
# rms := x->RCMatCyc(MatSPL(x));

# BRDFT3(4,a) -> 6a+3m
# BRDFT3(4,1/4) -> 6a+2m

# costs:
#         4, 6+2   = 8
# PRDFT3, 8, 22+10 = 32
#        16, 66+30 = 96
#
#           4, 8(1/4) or 9(other)
# BRDFT3, 8, 34
#           16, 104