#
# cls 67: A_2
#
handle67char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[67];
    info:=infos[67];

    rep:=Representative(o);
    #pr[19]=[0,0,1,1,1,0,0,0]
    #pr[17]=[0,1,1,1,0,0,0,0]
    #pr[37]=[1,1,1,2,1,0,0,0]
    tmp1:=ApplyRootsReflections(rep,[19,17,37]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,1]]);
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[3],[-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[19,17,37]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,1]]);
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[3],[-1]);

    return [tmp1,tmp2];
end;

#
# cls 65: A_2A_1
#
handle65char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[65];
    info:=infos[65];

    rep:=Representative(o);
    #pr[53]=[1,1,1,2,1,1,1,0]
    #pr[52]=[1,1,1,2,2,1,0,0]
    #pr[93]=[1,2,3,4,3,2,1,0]
    tmp1:=ApplyRootsReflections(rep,[53,52,93]);
    uu:=Unipotent(chevalleyAdj(rep),[[1,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1],[-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[53,52,93]);
    uu:=Unipotent(chevalleyAdj(cent),[[1,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1],[-1]);

    return [tmp1,tmp2];
end;

#
# cls 61: A_2^2
#
handle61char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[61];
    info:=infos[61];

    rep:=Representative(o);
    #pr[43]=[0,0,1,1,1,1,1,1]
    #pr[41]=[0,1,1,1,1,1,1,0]
    #pr[79]=[1,1,1,2,2,2,2,1]
    tmp1:=ApplyRootsReflections(rep,[43,41,79]);
    uu:=Unipotent(chevalleyAdj(rep),[[1,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[3],[-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[43,41,79]);
    uu:=Unipotent(chevalleyAdj(cent),[[1,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[3],[-1]);

    return [tmp1,tmp2];
end;

#
# cls 58: D_4(a_1)
#
handle58char3:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[58];
    info:=infos[58];

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

    #pr[26]=[0,1,0,1,1,1,0,0]
    #pr[27]=[0,0,1,1,1,1,0,0]
    tmp3:=ApplyRootsReflections(rep,[26,27]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,1],[4,1],[5,-1],[11,1],[12,-1]],Ordering(rep));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[2,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[4,51,52]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,-1],[5,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[4,1,2,6],[-1,-1,-1,-1]);

    tmp4:=0;
    tmp4:=ApplyRootsReflections(cent,[26,27]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,1],[4,1],[5,-1],[11,1],[12,-1]],Ordering(cent));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjugateByTori(tmp4,[2,4],[-1,-1,-1,-1]);

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

#
# cls 54: D_4(a_1)A_1 Ross' rep
#
handle54char3:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[54];
    info:=infos[54];

    rep:=Representative(o);

    #pr[23]=[1,1,1,1,0,0,0,0]
    #pr[24]=[1,0,1,1,1,0,0,0]
    tmp1:=ApplyRootsReflections(rep,[23,24]);
    tmp1:=ConjNegUa(tmp1,4,1);
    uu:=Unipotent(chevalleyAdj(rep),[[2,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2],[-1,-1,-1,-1]);

    #pr[65]=[1,1,2,2,1,1,1,1]
    #pr[67]=[1,1,1,2,2,1,1,1]
    tmp3:=ApplyRootsReflections(rep,[65,67,4]);
    uu:=Unipotent(chevalleyAdj(rep),[[12,-1],[11,-1]],Ordering(rep));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[1,2,4,6,8],[-1,-1,-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[23,24]);
    tmp2:=ConjNegUa(tmp2,4,1);
    uu:=Unipotent(chevalleyAdj(cent),[[2,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2],[-1,-1,-1,-1]);

    tmp4:=0;
    tmp4:=ApplyRootsReflections(cent,[65,67,4]);
    uu:=Unipotent(chevalleyAdj(cent),[[12,-1],[11,-1]],Ordering(cent));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjugateByTori(tmp4,[1,2,4,6,8],[-1,-1,-1,-1,-1]);

    return [tmp1,tmp3,tmp2,tmp4];
end;
#
# cls 54: D_4(a_1)A_1 ([2,5,9,10,7] in E_7) [2,5,10,11,7] in E_8
#
handle54char3old:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[54];
    info:=infos[54];

    rep:=Representative(o);

    #pr[23]=[1,1,1,1,0,0,0,0]
    #pr[24]=[1,0,1,1,1,0,0,0]
    tmp1:=ApplyRootsReflections(rep,[23,24]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[12,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,2],[-1,-1,-1,-1]);

    #pr[65]=[1,1,2,2,1,1,1,1]
    #pr[67]=[1,1,1,2,2,1,1,1]
    tmp3:=ApplyRootsReflections(rep,[65,67]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,-1]],Ordering(rep));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjNegUa(tmp3,4,1);
    uu:=Unipotent(chevalleyAdj(rep),[[3,-1],[4,-1],[5,-1],[12,1]],Ordering(rep));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[2,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[23,24]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[12,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,2],[-1,-1,-1,-1]);

    tmp4:=0;
    tmp4:=ApplyRootsReflections(cent,[65,67]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,-1]],Ordering(cent));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjNegUa(tmp4,4,1);
    uu:=Unipotent(chevalleyAdj(cent),[[3,-1],[4,-1],[5,-1],[12,1]],Ordering(cent));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjugateByTori(tmp4,[2,4],[-1,-1,-1,-1]);

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

#
# cls 53: A_3A_2 [3,4,2,6,7]
#
handle53char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[53];
    info:=infos[53];

    rep:=Representative(o);

    #pr[26]=[0,1,0,1,1,1,0,0]
    #pr[27]=[0,0,1,1,1,1,0,0]
    #pr[55]=[0,1,1,2,2,1,1,0]
    tmp1:=ApplyRootsReflections(rep,[26,27,55]);
    uu:=Unipotent(chevalleyAdj(rep),[[2,-1],[3,1],[6,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[4,6],[-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[26,27,55]);
    uu:=Unipotent(chevalleyAdj(cent),[[2,-1],[3,1],[6,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[4,6],[-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 52: A_4
#
handle52char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[52];
    info:=infos[52];

    rep:=Representative(o);

    #pr[33]=[0,1,1,1,1,1,0,0]
    #pr[31]=[1,0,1,1,1,1,0,0]
    #pr[64]=[1,1,2,2,2,1,1,0]
    #pr[89]=[1,2,2,4,3,2,1,0]
    tmp1:=ApplyRootsReflections(rep,[33,31,64,89]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,1],[4,1],[11,-1],[17,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,3,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[33,31,64,89]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,1],[4,1],[11,-1],[17,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,3,4],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;
#vals[1]:=vals[2];
#vals[3]:=-vals[1]+One(APR);
#vals[4]:=vals[2]+One(APR);
#vals[11]:=-(vals[2]^2-vals[2]-vals[10]+One(APR));
#vals[17]:=-(vals[9]-vals[10]+vals[16]+One(APR));
#
# cls 52: A_4 [1,2,3,4]
#
handle52char3old:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[52];
    info:=infos[52];

    rep:=Representative(o);

    #pr[33]=[0,1,1,1,1,1,0,0]
    #pr[31]=[1,0,1,1,1,1,0,0]
    #pr[64]=[1,1,2,2,2,1,1,0]
    #pr[89]=[1,2,2,4,3,2,1,0]
    tmp1:=ApplyRootsReflections(rep,[33,31,64,89]);
    uu:=Unipotent(chevalleyAdj(rep),[[2,-1],[4,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,3,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[33,31,64,89]);
    uu:=Unipotent(chevalleyAdj(cent),[[2,-1],[4,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,3,4],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 49: D_4(a_1)A_2
#
handle49char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[49];
    info:=infos[49];

    rep:=Representative(o);

    #pr[23]=[1,1,1,1,0,0,0,0]  asta merge si cu rep=[2,3,10,12,7,8] urmat de ConjNegUa(tmp1,4,1)
    #pr[24]=[1,0,1,1,1,0,0,0]
    tmp1:=ApplyRootsReflections(rep,[23,24]);
    tmp1:=ConjNegUa(tmp1,4,1);
    uu:=Unipotent(chevalleyAdj(rep),[[2,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[23,24]);
    tmp2:=ConjNegUa(tmp2,4,1);
    uu:=Unipotent(chevalleyAdj(cent),[[2,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;
#
# cls 49: D_4(a_1)A_2 [2,5,10,11,7,8] in E_8
#
handle49char3old:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[49];
    info:=infos[49];

    rep:=Representative(o);

    #pr[23]=[1,1,1,1,0,0,0,0]  asta merge si cu rep=[2,3,10,12,7,8] urmat de ConjNegUa(tmp1,4,1)
    #pr[24]=[1,0,1,1,1,0,0,0]
    tmp1:=ApplyRootsReflections(rep,[23,24]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[12,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,2],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[23,24]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[12,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,2],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 48: A_4A_1
#
handle48char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[48];
    info:=infos[48];

    rep:=Representative(o);

    #pr[41]=[0,1,1,1,1,1,1,0]
    #pr[39]=[1,0,1,1,1,1,1,0]
    #pr[57]=[1,1,2,2,2,1,0,0]
    #pr[89]=[1,2,2,4,3,2,1,0]
    tmp1:=ApplyRootsReflections(rep,[41,39,57,89]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,-1],[4,-1],[11,-1],[17,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2,3,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[41,39,57,89]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,-1],[4,-1],[11,-1],[17,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2,3,4],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;
#vals[2]:=vals[1];
#vals[3]:=vals[1]-One(APR);
#vals[4]:=-vals[2]-One(APR);
#vals[11]:=vals[1]-vals[9]-One(APR);
#vals[10]:=-vals[1]^2+vals[9];
#vals[17]:=vals[1]^2-vals[16]-One(APR);

#
# cls 46: D_5(a_1)
#
handle46char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[46];
    info:=infos[46];

    rep:=Representative(o);

    #pr[82]=[1,1,2,3,3,2,1,0]
    #pr[80]=[1,2,2,3,2,2,1,0]
    tmp1:=ApplyRootsReflections(rep,[82,80]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[12,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[82,80]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[12,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2,4],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 45: A_4A_1^2
#
handle45char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[45];
    info:=infos[45];

    rep:=Representative(o);

    #pr[71]=[1,1,2,2,2,2,1,0]
    #pr[72]=[1,1,2,2,2,1,1,1]
    #pr[112]=[1,3,3,5,4,3,2,1]
    #pr[111]=[2,2,3,5,4,3,2,1]
    tmp1:=ApplyRootsReflections(rep,[71,72,112,111]);
    uu:=Unipotent(chevalleyAdj(rep),[[1,1],[2,1],[3,-1],[9,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[5,8],[-1,aaa]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[71,72,112,111]);
    uu:=Unipotent(chevalleyAdj(cent),[[1,1],[2,1],[3,-1],[9,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[5,8],[-1,aaa]);

    return [tmp1,tmp2];
end;

#
# cls 40: D_4A_2
#
handle40char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[40];
    info:=infos[40];

    rep:=Representative(o);

    #pr[34]=[0,1,0,1,1,1,1,0]
    #pr[35]=[0,0,1,1,1,1,1,0]
    #pr[68]=[0,1,1,2,2,2,1,1]
    tmp1:=ApplyRootsReflections(rep,[34,35,68]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,-1],[4,-1],[5,-1],[8,-1],[11,-1],[12,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[4,6,7,8],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[34,35,68]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,-1],[4,-1],[5,-1],[8,-1],[11,-1],[12,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[4,6,7,8],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;
#vals[3]:=vals[2]-One(APR);
#vals[4]:=-vals[2]-One(APR);
#vals[5]:=vals[4];
#vals[8]:=-vals[7]-One(APR);
#vals[11]:=-vals[2]^2+vals[2]-vals[10]-One(APR);
#vals[12]:=vals[2]-vals[10]+One(APR);

#
# cls 39: E_6(a_3) [1,4,6,17,18,19] this doesn't work in char2
#
handle39char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[39];
    info:=infos[39];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;

#
# cls 34: D_6(a_2)
#
handle34char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[34];
    info:=infos[34];

    rep:=Representative(o);

    #pr[42]=[0,1,0,1,1,1,1,0]
    #pr[43]=[0,0,1,1,1,1,1,0]
    tmp1:=ApplyRootsReflections(rep,[42,43]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[6,1],[10,1],[13,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[4,5,8],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[42,43]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[6,1],[10,1],[13,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[4,5,8],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 33: E_6(a_3)A_1 [1,4,6,8,17,18,19] this doesn't work in char2
#
handle33char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[33];
    info:=infos[33];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;

#
# cls 32: E_7(a_5)
#
handle32char3:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[32];
    info:=infos[32];

    rep:=Representative(o);
    tmp1:=ConjNegUa(rep,1,1);
    tmp1:=ConjNegUa(tmp1,3,-1);
    tmp1:=ConjNegUa(tmp1,9,-1);
    tmp1:=ConjNegUa(tmp1,2,1);
    uu:=Unipotent(chevalleyAdj(rep),[[1,-1],[3,1],[5,-1],[6,-1],[9,1],[13,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    uu:=Unipotent(chevalleyAdj(rep),[[11,1],[14,1],[16,1],[20,1],[21,1],[25,1],[26,-1],[27,-1],[30,1],[33,-1],[35,-1],[37,-1],[38,-1],[39,1],[41,-1],[44,1],[45,1],[46,-1],[51,-1],[52,-1],[59,1],[64,-1],[66,1],[69,-1],[71,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTorus(tmp1,1,-1);

    tmp3:=ConjugateByTorus(rep,18,-1);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjNegUa(cent,1,1);
    tmp2:=ConjNegUa(tmp2,3,-1);
    tmp2:=ConjNegUa(tmp2,9,-1);
    tmp2:=ConjNegUa(tmp2,2,1);
    uu:=Unipotent(chevalleyAdj(cent),[[1,-1],[3,1],[5,-1],[6,-1],[9,1],[13,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    uu:=Unipotent(chevalleyAdj(cent),[[11,1],[14,1],[16,1],[20,1],[21,1],[25,1],[26,-1],[27,-1],[30,1],[33,-1],[35,-1],[37,-1],[38,-1],[39,1],[41,-1],[44,1],[45,1],[46,-1],[51,-1],[52,-1],[59,1],[64,-1],[66,1],[69,-1],[71,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTorus(tmp2,1,-1);

    tmp4:=0;
    tmp4:=ConjugateByTorus(cent,18,-1);

    return [tmp1,tmp3,tmp2,tmp4,uu];
end;

#
# cls 30: E_8(a_7) (NOT FINISHED)
#
handle30char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[30];
    info:=infos[30];

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

    tmp2:=0;
    #cent:=FromPositiveBorel(o,info[2]);
    #tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;

#
# cls 28: D_6(a_1)
#
handle28char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[28];
    info:=infos[28];

    rep:=Representative(o);

    #pr[42]=[0,1,0,1,1,1,1,0]
    #pr[43]=[0,0,1,1,1,1,1,0]
    tmp1:=ApplyRootsReflections(rep,[42,43]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[11,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[3,4],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[42,43]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[11,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[3,4],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 26: E_7(a_4) [1,4,7,14,17,18,19,26] this doesn't work in char2
#
handle26char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[26];
    info:=infos[26];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;

#
# cls 25: E_6(a_1)
#
handle25char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[25];
    info:=infos[25];

    #pr[58]=[1,1,2,2,1,1,1,0]
    #pr[59]=[1,1,1,2,2,1,1,0]
    #pr[61]=[0,1,1,2,2,2,1,0]

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[58,59,61]);
    tmp1:=ConjNegUa(tmp1,4,-1);
    uu:=Unipotent(chevalleyAdj(rep),[[2,1],[5,-1],[10,-1],[11,-1],[12,1],[18,1],[19,1],[26,-1],[27,-1],[32,-1],[40,1],[48,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[5,7],[-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[58,59,61]);
    tmp2:=ConjNegUa(tmp2,4,-1);
    uu:=Unipotent(chevalleyAdj(cent),[[2,1],[5,-1],[10,-1],[11,-1],[12,1],[18,1],[19,1],[26,-1],[27,-1],[32,-1],[40,1],[48,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[5,7],[-1,-1]);

    return [tmp1,tmp2,uu];
end;

#
# cls 24: D_5A_2
#
handle24char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[24];
    info:=infos[24];

    rep:=Representative(o);

    #pr[87]=[1,1,2,3,3,2,1,1]
    #pr[86]=[1,2,2,3,2,2,1,1]
    #pr[119]=[2,3,4,6,5,4,3,1]
    tmp1:=ApplyRootsReflections(rep,[87,86,119]);
    uu:=Unipotent(chevalleyAdj(rep),[[8,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,4,7],[-1,-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[87,86,119]);
    uu:=Unipotent(chevalleyAdj(cent),[[8,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,4,7],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;

#
# cls 21: D_7(a_2)
#
handle21char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[21];
    info:=infos[21];

    rep:=Representative(o);

    #pr[113]=[2,3,3,5,4,3,2,1]
    #pr[114]=[2,2,4,5,4,3,2,1]
    tmp1:=ApplyRootsReflections(rep,[113,114]);
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[6,1],[11,-1],[13,-1],[20,-1],[27,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[5,6],[-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[113,114]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[6,1],[11,-1],[13,-1],[20,-1],[27,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[5,6],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;
#
# THIS IS FOR THE D_7(a_2) class in E_8 characteristic 3
#
#set:=Filtered(subs,i->i<=63);
#Sort(set);
#uu:=Unipotent(alg,List(set,i->[i,avars[i]]),Ordering(rep));
#
#vals:=ShallowCopy(avars);
#vals[4]:=0;
#vals[3]:=vals[2];
#vals[5]:=vals[3]-vals[2];
#vals[6]:=One(APR);
#vals[7]:=0;
#vals[8]:=vals[2];
#vals[11]:=-vals[3]-vals[10]-One(APR);
#vals[12]:=vals[3];
#vals[13]:=-vals[2]+vals[10]-One(APR);
#vals[14]:=vals[8];
#vals[17]:=vals[2]^2+vals[2]-vals[15];
#vals[18]:=-vals[2]-vals[17];
#vals[19]:=vals[3]^2-vals[2]-vals[18];
#vals[20]:=vals[13]+vals[10];
#vals[21]:=vals[15];
#vals[22]:=-vals[2]^2-vals[2]+vals[15]-vals[17];
#vals[26]:=-vals[2]*vals[10]-vals[10]^2-vals[10]+vals[17];
#vals[27]:=vals[2]+vals[10]+vals[17]-vals[26]+One(APR);
#vals[28]:=vals[18]+vals[15];
#vals[32]:=-(-vals[2]^3-vals[2]+vals[15]-vals[25]-vals[29]);
#vals[33]:=-(vals[2]^2*vals[10]-vals[2]^2+vals[2]*vals[10]-vals[2]-vals[15]-vals[32]);
#vals[34]:=vals[2]^2+vals[25]+vals[32];
#vals[35]:=-vals[2]^3-vals[2]^2-vals[2]+vals[15]+vals[25]-vals[34];
#vals[36]:=vals[2]*vals[15]+vals[10]*vals[15]+vals[15]+vals[29]+vals[34];
#vals[41]:=-(vals[2]^4+vals[2]^3+vals[2]^2-vals[15]^2+vals[25]);
#vals[43]:=vals[2]^3-vals[2]^2*vals[15]+vals[2]^2-vals[2]*vals[15]+vals[15]^2+vals[2]-vals[15]-vals[25]+vals[34]+vals[35]-vals[36]+vals[41]-vals[42];
#vals[48]:=-vals[2]^4+vals[2]^3*vals[10]+vals[2]^3-vals[2]^2*vals[15]+vals[2]*vals[10]*vals[15]+vals[10]^2*vals[15]+vals[2]*vals[10]-vals[10]*vals[15]-vals[15]^2-vals[2]-vals[25]+vals[29]+vals[42];
#vals[49]:=-(vals[2]^4+vals[2]^3+vals[2]^2*vals[15]+vals[2]*vals[10]*vals[15]+vals[10]^2*vals[15]-vals[2]*vals[15]+vals[10]*vals[15]+vals[15]^2+vals[41]-vals[42]);
#vals[55]:=-(-vals[2]^4-vals[2]^2*vals[10]*vals[15]-vals[2]^3+vals[2]^2*vals[15]-vals[2]^2*vals[25]-vals[2]*vals[10]*vals[15]-vals[2]^2+vals[2]*vals[15]+vals[2]*vals[25]-vals[15]^2-vals[15]*vals[25]-vals[25]+vals[50]);
#vals[61]:=-(vals[2]^4*vals[10]+vals[2]^3*vals[10]-vals[2]^2*vals[10]*vals[15]+vals[2]^3+vals[2]^2*vals[10]+vals[2]^2*vals[15]-vals[2]*vals[10]*vals[15]-vals[2]*vals[10]-vals[2]*vals[15]-vals[10]*vals[15]-vals[15]^2-vals[2]-vals[15]-vals[32]-vals[41]+vals[42]+vals[48]-vals[49]+vals[56]);
#vals[62]:=-(-vals[2]^6+vals[2]^5+vals[2]^3*vals[10]*vals[15]-vals[2]^3*vals[15]+vals[2]^3*vals[25]-vals[2]^3*vals[29]+vals[2]^2*vals[10]*vals[15]-vals[2]*vals[10]*vals[15]^2-vals[10]^2*vals[15]^2+vals[2]^3+vals[2]^2*vals[15]+vals[2]^2*vals[25]-vals[2]*vals[10]*vals[15]+vals[2]*vals[15]^2+vals[2]*vals[15]*vals[25]+vals[10]*vals[15]*vals[25]-vals[15]^3-vals[2]^2+vals[2]*vals[15]+vals[2]*vals[25]-vals[2]*vals[29]+vals[15]*vals[29]+vals[25]^2-vals[25]*vals[29]+vals[29]^2+vals[25]-vals[50]);
#
#vv:=Value(uu,avars,vals);
#vvv:=Value(vv,avars,List(avars,i->0));

#
# cls 18: E_6(a_1)A_1
#
handle18char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[18];
    info:=infos[18];

    #pr[106]=[1,2,2,4,4,3,2,1]
    #pr[105]=[1,2,3,4,3,3,2,1]
    #pr[104]=[2,2,3,4,3,2,2,1]

    # this is for E_6(a_1)A_1
    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[106,105,104]);
    tmp1:=ConjNegUa(tmp1,4,-1);
    uu:=Unipotent(chevalleyAdj(rep),[[2,1],[11,1],[12,-1],[5,1],[10,-1],[18,-1],[19,-1],[26,-1],[27,1],[32,1],[40,-1],[48,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,2,3,5,6],[-1,-1,-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[106,105,104]);
    tmp2:=ConjNegUa(tmp2,4,-1);
    uu:=Unipotent(chevalleyAdj(cent),[[2,1],[11,1],[12,-1],[5,1],[10,-1],[18,-1],[19,-1],[26,-1],[27,1],[32,1],[40,-1],[48,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,2,3,5,6],[-1,-1,-1,-1,-1]);

    return [tmp1,tmp2,uu];
end;
#
# THIS IS FOR THE E_6(a_1)A_1 class in E_7 characteristic 3
#
#set:=List(Filtered(pr,i->i[7]=0 and i[8]=0),j->Position(pr,j));
#uu:=Unipotent(alg,List(set,i->[i,avars[i]]),Ordering(rep));
#vals:=ShallowCopy(avars);
#vals[2]:=One(APR)-vals[1];
#vals[3]:=0;
#vals[4]:=0;
#vals[5]:=vals[1]+One(APR);
#vals[6]:=vals[5]-One(APR);
#vals[11]:=One(APR)-vals[1];
#vals[10]:=-One(APR);
#vals[12]:=-vals[1]-vals[10]+One(APR);
#vals[13]:=-vals[1]^2-vals[1]-vals[9];
#vals[16]:=-(-vals[1]^2+vals[1]+vals[9]);
#vals[17]:=-(-vals[1]^2+vals[1]-vals[16]);
#vals[18]:=-vals[1]^2+vals[1]+vals[9]-One(APR);
#vals[19]:=-(-vals[1]^2-vals[1]+vals[9]-vals[16]-One(APR));
#vals[20]:=vals[1]^2-vals[18]-One(APR);
#vals[24]:=-(vals[1]^3-vals[1]^2+vals[1]*vals[9]+vals[9]-vals[23]);
#vals[25]:=-(vals[1]^3-vals[1]^2-vals[1]*vals[9]-vals[9]-vals[23]-vals[24]);
#vals[26]:=-vals[1]^2-vals[23]-One(APR);
#vals[27]:=-(vals[1]^3+vals[1]^2-vals[1]*vals[9]-vals[1]-vals[9]-vals[24]-One(APR));
#vals[30]:=-(vals[1]^4+vals[1]^3+vals[1]^2*vals[9]+vals[1]^2-vals[1]*vals[9]+vals[9]^2-vals[23]);
#vals[32]:=-(vals[1]^3+vals[1]^2-vals[9]-vals[23]+One(APR));
#vals[33]:=-(vals[1]^4-vals[1]^3-vals[1]^2-vals[1]*vals[9]+vals[1]+vals[24]-vals[30]-vals[31]);
#vals[37]:=-vals[1]^4+vals[1]^3+vals[1]^2*vals[9]-vals[1]*vals[9]-vals[9]^2+vals[9]-vals[23]+vals[31];
#vals[40]:=-(vals[1]^4+vals[1]^3-vals[1]^2*vals[9]+vals[1]^2+vals[1]*vals[9]-vals[1]-vals[9]-vals[37]+One(APR));
#vals[44]:=vals[1]^4-vals[1]^3*vals[9]+vals[1]^3+vals[1]^2*vals[9]+vals[1]*vals[9]^2+vals[1]^2-vals[9]^2-vals[23]+vals[38];
#vals[48]:=-(-vals[1]^5-vals[1]^4-vals[1]^3*vals[9]-vals[1]^2*vals[9]-vals[1]^2+vals[1]*vals[9]-vals[9]^2-vals[1]+vals[9]-vals[23]+vals[37]+vals[38]-vals[45]+One(APR));
#vals[51]:=-vals[1]^4-vals[1]^3*vals[23]+vals[1]^2*vals[9]^2-vals[1]^3+vals[1]*vals[9]^2-vals[1]^2-vals[1]*vals[23]-vals[9]^2+vals[23]^2+vals[23]+vals[38];
#vals[52]:=vals[1]^6+vals[1]^5-vals[1]^4*vals[9]+vals[1]^4+vals[1]^3*vals[9]+vals[1]^2*vals[9]^2+vals[1]^3-vals[1]^2*vals[9]-vals[1]^2*vals[23]-vals[1]*vals[9]^2+vals[9]^3-vals[1]^2+vals[9]^2+vals[23]^2+vals[23]-vals[31]-vals[38]+vals[45];
#vals[63]:=-(-vals[1]^6-vals[1]^5*vals[9]-vals[1]^4*vals[9]-vals[1]^3*vals[9]^2-vals[1]^3*vals[9]-vals[1]^3*vals[23]-vals[1]^3*vals[31]-vals[1]*vals[9]^3+vals[1]^3-vals[1]^2*vals[9]+vals[1]^2*vals[23]-vals[1]^2*vals[31]-vals[9]^3-vals[9]^2*vals[23]-vals[1]*vals[9]-vals[1]*vals[23]+vals[9]^2+vals[9]*vals[31]+vals[23]^2+vals[23]*vals[31]-vals[9]+vals[23]-vals[31]);
#
#vv:=Value(uu,avars,vals);
#vvv:=Value(vv,avars,List(avars,i->0));

#
# cls 17: E_7(a_3) ([1,4,6,7,15,16,17] in E_7) [1,4,6,7,17,18,19] in E_8 this doesn't work in char2
#
handle17char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[17];
    info:=infos[17];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;

# This is for Seitz' representative in first table
#
# cls 16: E_8(b_6)
#
#handle16char3:=function(orbs,infos)
#    local tmp1,tmp2,o,info,rep,cent;
#    o:=Classes(orbs)[16];
#    #info:=infos[16];
#
#    rep:=Representative(o);
#    tmp1:=ConjugateByTori(rep,[3,4,7,8],[-1,-1,-1,-1]);# same as [11,15] should be Z(D_8)
#
#    tmp2:=0;
#    #cent:=FromPositiveBorel(o,info[2]);
#    #tmp2:=ConjugateByTori(cent,[3,4,7,8],[-1,-1,-1,-1]);# same as [11,15] should be Z(D_8)
#
#    return [tmp1,tmp2];
#end;

# This is for Seitz' representative used in proof
#
# cls 16: E_8(b_6)
#
handle16char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[16];
    info:=infos[16];

    rep:=Representative(o);
    tmp1:=ConjugateByTori(rep,[12,13,15],[-1,-1,-1,-1]);# should be Z(D_8)

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTori(cent,[12,13,15],[-1,-1,-1,-1]);# should be Z(D_8)

    return [tmp1,tmp2];
end;

#
# cls 15: D_7(a_1)
#
handle15char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[15];
    info:=infos[15];

    rep:=Representative(o);

    #pr[113]=[2,3,3,5,4,3,2,1]
    #pr[114]=[0,0,1,1,1,1,1,0]
    tmp1:=ApplyRootsReflections(rep,[113,114]);
    #uu:=Unipotent(chevalleyAdj(rep),[[4,1],[10,-1]],Ordering(rep)); si asta merge
    uu:=Unipotent(chevalleyAdj(rep),[[4,1],[11,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2,3,4],[aaa,aaa,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[113,114]);
    uu:=Unipotent(chevalleyAdj(cent),[[4,1],[11,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2,3,4],[aaa,aaa,-1]);

    return [tmp1,tmp2];
end;

#
# cls 12: E_8(a_6)
#
handle12char3:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu,vars;
    o:=Classes(orbs)[12];
    info:=infos[12];

    # L(Q)_2 has dimension 24 and should correspond to [ 4, 12, 14, 15, 21, 23, 25, 26, 31, 37, 46, 47, 53, 55, 57, 72, 73, 82, 90, 92, 97, 104, 106, 113 ]
    rep:=Representative(o);

    vars:=ShallowCopy(avars);#List([1..120],i->avars[i]);
    #vars[5]:=vars[1]*vars[2]*vars[3]-vars[1]*vars[3]*vars[13]-vars[2]*vars[3]*vars[5]+vars[2]*vars[9]-vars[2]*vars[13]-vars[9]*vars[13];
    vars[9]:=One(APR);
    vars[1]:=-vars[9]^2;#0;
    vars[2]:=vars[9]^3;
    #vars[9]:=One(APR);# sau -1
    vars[3]:=0;#-vars[9];
    vars[5]:=vars[1];
    #vars[23]:=-vars[9];
    #vars[2]:=vars[3]*vars[9]*vars[23]-vars[9]^2*vars[23];
    #vars[5]:=1;
    vars[26]:=-One(APR);
    vars[21]:=Zero(APR);
    vars[13]:=vars[1]*vars[3]-vars[3]*vars[5]+vars[9];
    vars[23]:=-vars[9]^3;
    vars[29]:=-(vars[9]^3*vars[21]+vars[2]-vars[9]);#0=-vars[21];

    tmp1:=ConjNegUa(rep,2,vars[2]);#1
    tmp1:=ConjNegUa(tmp1,5,vars[5]);#-1
    #tmp1:=ConjNegUa(tmp1,6,avars[6]);
    tmp1:=ConjNegUa(tmp1,13,vars[13]);#1
    #uu:=Unipotent(chevalleyAdj(rep),[[6,One(APR)]],Ordering(rep));
    #tmp1:=Conj(tmp1,uu);
    tmp1:=ConjNegUa(tmp1,1,vars[1]);#-1
    tmp1:=ConjNegUa(tmp1,3,vars[3]);#0
    tmp1:=ConjNegUa(tmp1,9,vars[9]);#1
    uu:=Unipotent(chevalleyAdj(rep),[[1,vars[21]],[3,vars[23]],[6,vars[26]],[9,vars[29]]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    uu:=Unipotent(chevalleyAdj(rep),[[4, -1 ], [ 8, -1 ], [ 10, 1 ], [ 11, -1 ], [ 14, 1 ], [ 15, 1 ], [ 16, 1 ], [ 19, -1 ], [ 21, -1 ], [ 22, -1 ], [ 23, -1 ], [ 25, 1 ], [ 26, 1 ], [ 27, -1 ], [ 28, 1 ], [ 30, -1 ], [ 31, 1 ], [ 32, 1 ], [ 33, -1 ], [ 34, 1 ], [ 36, -1 ], [ 37, -1 ], [ 40, -1 ], [ 41, 1 ], [ 42, 1 ], [ 44, 1 ], [ 47, -1 ], [ 50, 1 ], [ 52, -1 ], [ 54, 1 ], [ 55, -1 ], [ 57, -1 ], [ 61, -1 ], [ 62, 1 ], [ 65, 1 ], [ 69, 1 ], [ 76, -1 ], [ 77, -1 ], [ 78, 1 ], [ 81, -1 ], [ 82, -1 ], [ 83, 1 ], [ 85, -1 ], [ 86, -1 ], [ 91, 1 ], [ 93, -1 ], [ 94, 1 ], [ 97, 1 ], [ 98, -1 ], [ 102, -1 ], [ 103, -1 ], [ 106, 1 ], [ 107, -1 ], [ 108, 1 ], [ 109, -1 ], [ 111, -1 ], [ 114, -1 ], [ 115, -1 ], [ 117, 1 ], [ 118, -1 ] ],Ordering(rep));
    #uu:=Unipotent(chevalleyAdj(rep),[[1,avars[1]],[9,avars[9]]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    #tmp1:=ConjNegUa(tmp1,3,avars[3]);

    tmp3:=ConjugateByTori(rep,[4,6,7],[-1,-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjNegUa(cent,2,vars[2]);
    tmp2:=ConjNegUa(tmp2,5,vars[5]);
    tmp2:=ConjNegUa(tmp2,13,vars[13]);
    tmp2:=ConjNegUa(tmp2,1,vars[1]);
    tmp2:=ConjNegUa(tmp2,3,vars[3]);
    tmp2:=ConjNegUa(tmp2,9,vars[9]);
    uu:=Unipotent(chevalleyAdj(cent),[[1,vars[21]],[3,vars[23]],[6,vars[26]],[9,vars[29]]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    uu:=Unipotent(chevalleyAdj(cent),[[4, -1 ], [ 8, -1 ], [ 10, 1 ], [ 11, -1 ], [ 14, 1 ], [ 15, 1 ], [ 16, 1 ], [ 19, -1 ], [ 21, -1 ], [ 22, -1 ], [ 23, -1 ], [ 25, 1 ], [ 26, 1 ], [ 27, -1 ], [ 28, 1 ], [ 30, -1 ], [ 31, 1 ], [ 32, 1 ], [ 33, -1 ], [ 34, 1 ], [ 36, -1 ], [ 37, -1 ], [ 40, -1 ], [ 41, 1 ], [ 42, 1 ], [ 44, 1 ], [ 47, -1 ], [ 50, 1 ], [ 52, -1 ], [ 54, 1 ], [ 55, -1 ], [ 57, -1 ], [ 61, -1 ], [ 62, 1 ], [ 65, 1 ], [ 69, 1 ], [ 76, -1 ], [ 77, -1 ], [ 78, 1 ], [ 81, -1 ], [ 82, -1 ], [ 83, 1 ], [ 85, -1 ], [ 86, -1 ], [ 91, 1 ], [ 93, -1 ], [ 94, 1 ], [ 97, 1 ], [ 98, -1 ], [ 102, -1 ], [ 103, -1 ], [ 106, 1 ], [ 107, -1 ], [ 108, 1 ], [ 109, -1 ], [ 111, -1 ], [ 114, -1 ], [ 115, -1 ], [ 117, 1 ], [ 118, -1 ] ],Ordering(cent));
    tmp2:=Conj(tmp2,uu);

    tmp4:=0;
    tmp4:=ConjugateByTori(cent,[4,6,7],[-1,-1,-1]);

    return [tmp1,tmp3,tmp2,tmp4,uu];
end;

#
# cls 10: E_8(b_5)
#
handle10char3:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[10];
    info:=infos[10];

    rep:=Representative(o);
    tmp1:=ConjNegUa(rep,1,1);
    tmp1:=ConjNegUa(tmp1,3,-1);
    tmp1:=ConjNegUa(tmp1,9,-1);
    tmp1:=ConjNegUa(tmp1,2,1);
    uu:=Unipotent(chevalleyAdj(rep),[[1,-1],[3,1],[5,-1],[6,-1],[9,1],[13,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    uu:=Unipotent(chevalleyAdj(rep),[[8,1],[10,-1],[11,1],[16,-1],[20,1],[21,1],[25,1],[27,-1],[30,-1],[31,1],[32,-1],[33,-1],[37,1],[38,-1],[40,1],[44,-1],[46,-1],[52,1],[55,-1],[59,1],[61,1],[63,1],[64,-1],[69,-1],[82,-1],[90,1],[93,1],[97,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTorus(tmp1,1,-1);

    tmp3:=ConjugateByTorus(rep,18,-1);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjNegUa(cent,1,1);
    tmp2:=ConjNegUa(tmp2,3,-1);
    tmp2:=ConjNegUa(tmp2,9,-1);
    tmp2:=ConjNegUa(tmp2,2,1);
    uu:=Unipotent(chevalleyAdj(cent),[[1,-1],[3,1],[5,-1],[6,-1],[9,1],[13,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    uu:=Unipotent(chevalleyAdj(cent),[[8,1],[10,-1],[11,1],[16,-1],[20,1],[21,1],[25,1],[27,-1],[30,-1],[31,1],[32,-1],[33,-1],[37,1],[38,-1],[40,1],[44,-1],[46,-1],[52,1],[55,-1],[59,1],[61,1],[63,1],[64,-1],[69,-1],[82,-1],[90,1],[93,1],[97,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTorus(tmp2,1,-1);

    tmp4:=0;
    tmp4:=ConjugateByTorus(cent,18,-1);

    return [tmp1,tmp3,tmp2,tmp4,uu];
end;

# For representative deduced from Seitz' E_8(b_6) given in first table
#
# cls 8: E_8(a_5)
#
#handle8char3:=function(orbs,infos)
#    local tmp1,tmp2,o,info,rep,cent;
#    o:=Classes(orbs)[8];
#    #info:=infos[8];
#
#    rep:=Representative(o);
#    tmp1:=ConjugateByTori(rep,[3,4,7,8],[-1,-1,-1,-1]);# same as [11,15] should be Z(D_8)
#
#    tmp2:=0;
#    #cent:=FromPositiveBorel(o,info[2]);
#    #tmp2:=ConjugateByTori(cent,[3,4,7,8],[-1,-1,-1,-1]);# same as [11,15] should be Z(D_8)
#
#    return [tmp1,tmp2];
#end;

# For representative deduced from Seitz' E_8(a_6) used in proof
#
# cls 8: E_8(a_5)
#
handle8char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[8];
    info:=infos[8];

    rep:=Representative(o);
    tmp1:=ConjugateByTori(rep,[4,6,7],[-1,-1,-1,-1]);# should be Z(D_8)

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTori(cent,[4,6,7],[-1,-1,-1,-1]);# should be Z(D_8)

    return [tmp1,tmp2];
end;

#
# cls 7: E_8(b_4) [1,4,7,8,14,17,19,26]  this doesn't work in char2
#
handle7char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[7];
    info:=infos[7];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;

#
# cls 5: E_8(a_4) [1,4,6,8,17,18,19,21]  this doesn't work in char2
#
handle5char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[5];
    info:=infos[5];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTori(cent,[4,8],[-1,-1]); # h_4, h_8

    return [tmp1,tmp2];
end;

#
# cls 4: E_8(a_3) [1,4,6,7,8,17,18,19]  this doesn't work in char2
#
handle4char3:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[4];
    info:=infos[4];

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

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjugateByTorus(cent,4,-1);# h_1 --> h_4

    return [tmp1,tmp2];
end;


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

RewriteRules(RulesStrengthReduce, rec(
    re_vpack := Rule([re, vpack], x -> ApplyFunc(vpack, List(x.args[1].args, re))),
    im_vpack := Rule([im, vpack], x -> ApplyFunc(vpack, List(x.args[1].args, im))),

    velem_vpack := Rule([velem, @(1, vpack), @(2, Value)], x -> @(1).val.args[1+@(2).val.v]),

    velem_vdup := Rule([velem, @(1, vdup), @(2)], x -> @(1).val.args[1]),

    fStretch_Drop := Rule( @(1, fStretch, x -> x.num = x.den), e -> e.func),
));


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

#sin(a+b) = sin(a)cos(b) + cos(a)sin(b)
#cos(a+b) = cos(a)cos(b) - sin(a)sin(b)

# cos(a)x + sin(a)y
# cos(a+b)x + sin(a+b)y
# cos(a)cos(b)x - sin(a)sin(b)x + sin(a)cos(b)y + cos(a)sin(b)y
# cos(a)cos(b)x + sin(a)cos(b)y + cos(a)sin(b)y - sin(a)sin(b)x
# cos(b)[cos(a)x+sin(a)y] + sin(b)[cos(a)y-sin(a)x]

NewRulesFor(HF, rec(
  HF_dummy := rec(
    applicable := (self, nt) >> true,
    forTransposition := false,
    apply := (t, c, nt) ->
     let(
         size := When(IsArrayT(t.params[1].t), t.params[1].t.size, Rows(t.params[1])),
       GathH(8, size, t.params[2]*size, 1)
    )
  )
));

NewRulesFor(TForAllInd, rec(
  TForAllInd_loop := rec(
    forTransposition := false,
    applicable := (self, nt) >> true,
    children := nt -> [ [ nt.params[2].withTags(nt.getTags()) ] ],
    apply := (t, c, nt) ->
      let(i := t.params[1],
          IterVStack(i, i.range,
	    OLCompose(
	      c[1],
	      GathH(Cols(c[1]), Cols(c[1]), Cols(c[1])*i, 1)
	     )
	  )
      )
  )
));

NewRulesFor(TComputeDist, rec(
  TComputeDistBase := rec(
    apply := (t, c, nt) ->
      let(x := var.fresh_t("r", TReal), y:= var.fresh_t("r", TReal),
      OLCompose(
        Reduction(t.params[1], (a,b)->min(a,b), V(1000.0), False),
	BinOp(t.params[1], Lambda([x, y], add(x, y)))
      )
  ))
));

NewRulesFor(TForAnyInd, rec(
    TForAnyInd_loop := rec(
        forTransposition := false,
        applicable := (self, nt) >> true,
        children := nt -> [ [ nt.params[2].withTags(nt.getTags()) ] ],
        apply := (t, c, nt) ->
	   let(iter := t.params[1],
	     OLCompose(
	       Reduction(iter.range, (a,b)->logic_or(a,b), V(false),
	       a->eq(a, V(true))), IterVStack(iter, c[1]))),
    )
));

NewRulesFor(TInsidePoly, rec(
    TInsidePoly_EvalLinSys := rec(
        applicable := (self, nt) >> ObjId(nt.params[1]) = LinearSystem,
        forTransposition := false,
        children := nt -> [ [ TEvalLinSys(nt.params[1]).withTags(nt.getTags()) ] ],
        apply := (t, C, Nonterms) -> let(
            evalsys := C[1],
            i := Ind(Rows(evalsys)), x := var.fresh_t("r", TDouble),
            cmp0 := PointWise(Rows(evalsys), Lambda([x,i], leq(x, V(0)))),
            forall_op := Reduction(Rows(evalsys), (a,b)->logic_and(a,b), V(true), a->eq(a, V(false))),
            OLCompose(forall_op, cmp0, evalsys))
    )
));

NewRulesFor(TEvalLinSys, rec(
    TEvalLinSys_TMatVecProd := rec(
        applicable := (self, nt) >> ObjId(nt.params[1]) = LinearSystem,
        children := nt -> [ [ TMatVecProd(nt.params[1].mat).withTags(nt.getTags()) ] ],
        forTransposition := false,
        apply := (t, C, Nonterms) -> let(linsys := t.params[1], A := linsys.mat, b := linsys.rhs, n :=linsys.dims()[1],
            x := var.fresh_t("r", TDouble), i := Ind(n),
            OLCompose(PointWise(n, Lambda([x,i], sub(x, nth(b, i)))), C[1]))
    )
));

NewRulesFor(TMatVecProd, rec(
    TMatVecProd_Term := rec(
        applicable := (self, nt) >> true,
        forTransposition := false,
        apply := (t, C, Nonterms) -> let(mat := t.params[1], n := mat.dims()[1], m := mat.dims()[2], i := Ind(n),
            IterVStack(i, n, ScalarProd(m, nth(mat, i))))
    )
));

NewRulesFor(TInfinityNorm, rec(
    TInfinityNorm_Base := rec(
        applicable := True,
        forTransposition := false,
        apply := (t, C, Nonterms) -> let(n :=t.dims()[2],
            x := var.fresh_t("r", TDouble), i := Ind(n),
            OLCompose(Reduction(n, (a,b)->max(a,b), V(0.0), False), PointWise(n, Lambda([x,i], abs(x)))))
    )
));

NewRulesFor(TDistance, rec(
    TDistance_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [[nt.params[1]]],
        apply := (t, C, Nonterms) -> let(n :=C[1].dims()[2], x := var.fresh_t("r", TDouble), y := var.fresh_t("r", TDouble),
            OLCompose(C[1], BinOp(n, Lambda([x,y], sub(x,y)))))
    )
));

NewRulesFor(TEvalPolynomial, rec(
    TEvalPolynomial_Base := rec(
        applicable := True,
        forTransposition := false,
        apply := (t, C, Nonterms) -> let(n := t.params[1]+1, x := var.fresh_t("r", TDouble), y := var.fresh_t("r", TDouble),
            OLCompose(ScalarProd(n, t.params[2]), Induction(n, Lambda([x,y], mul(x, y)), V(1.0))))
    )
));

NewRulesFor(TLess, rec(
    TLess_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [nt.params],
        apply := (t, c, Nonterms) -> let(
			n :=t.dims()[1],
			x := var.fresh_t("r", TDouble),
			y := var.fresh_t("r", TDouble),
			OLCompose(
				BinOp(n, Lambda([x,y], lt(x,y))), DirectSum(c[1], c[2]) )
    )
)));

NewRulesFor(TGreater, rec(
    TGreater_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [nt.params],
        apply := (t, c, Nonterms) -> let(
      n :=t.dims()[1],
      x := var.fresh_t("r", TDouble),
      y := var.fresh_t("r", TDouble),
      OLCompose(
        BinOp(n, Lambda([x,y], gt(x,y))), DirectSum(c[1], c[2]) )
    )
)));

NewRulesFor(TLessEqual, rec(
    TLessEqual_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [nt.params],
        apply := (t, c, Nonterms) -> let(
      n :=t.dims()[1],
      x := var.fresh_t("r", TDouble),
      y := var.fresh_t("r", TDouble),
      OLCompose(
        BinOp(n, Lambda([x,y], leq(x,y))), DirectSum(c[1], c[2]) )
    )
)));

NewRulesFor(TGreaterEqual, rec(
    TGreaterEqual_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [nt.params],
        apply := (t, c, Nonterms) -> let(
      n :=t.dims()[1],
      x := var.fresh_t("r", TDouble),
      y := var.fresh_t("r", TDouble),
      OLCompose(
        BinOp(n, Lambda([x,y], geq(x,y))), DirectSum(c[1], c[2]) )
    )
)));

NewRulesFor(TEqual, rec(
    TEqual_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [nt.params],
        apply := (t, c, Nonterms) -> let(
      n :=t.dims()[1],
      x := var.fresh_t("r", TDouble),
      y := var.fresh_t("r", TDouble),
      OLCompose(
        BinOp(n, Lambda([x,y], eq(x,y))), DirectSum(c[1], c[2]) )
    )
)));

NewRulesFor(TCond, rec(
	Cond_Base := rec(
		applicable := True,
		forTransposition := false,
		children := nt -> [nt.params],
		apply := (t, c, Nonterms) -> let(
			COND(c[1], c[2], c[3])
		)
	)
));

NewRulesFor(TConstant, rec(
    TConstant_Base := rec(
       applicable := True,
       forTransposition := false,
       children := nt -> [nt.params],
       apply := (t, C, Nonterms) -> let(x := var.fresh_t("r", TDouble), i := Ind(1), PointWise(1, Lambda([x, i], x)))
    )
));


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

ImportAll(paradigms.smp); # for AParSMP
ImportAll(paradigms.vector); # for VRCLR
Import(approx); # for CeilingRat.

Load(spiral.paradigms.dram.hacks); # for TTensor3 etc.
Import(hacks);

#_swrap := (nt) -> let(t:=nt.firstTag(), ScratchWrap(t.size,t.nsgmts,t.linesize));

#_nodeFitsTensorDoesnt := (nt) -> let (
#    k := nt.getTag(1).size,
#    m := nt.params[1].dims()[2],
#    n := nt.params[2],
#
#    2*m <= k and m*n >= k
#);

#_isALStoreTag := (nt) -> nt.isTag(1,ALStore) or nt.isTag(1,ALStoreCx);

_isADramTag := (nt) -> nt.isTag(1,ADram);
_isATileTag := (nt) -> nt.isTag(1,ATile);
_isATileRdTag := (nt) -> nt.isTag(1,ATileRd);
_isATileWrTag := (nt) -> nt.isTag(1,ATileWr);
_isACubeTag := (nt) -> nt.isTag(1,ACube);
_isACubeRdTag := (nt) -> nt.isTag(1,ACubeRd);
_isACubeWrTag := (nt) -> nt.isTag(1,ACubeWr);
_isTL_I := (nt) -> nt.params[1]=1 and nt.params[2]=1 and nt.params[3]>1 and nt.params[4]=1;
_isTL_L := (nt) -> nt.params[1]>1 and nt.params[2]>1 and nt.params[3]=1 and nt.params[4]=1;
_isTL_I_L := (nt) -> nt.params[1]>1 and nt.params[2]>1 and nt.params[3]>1 and nt.params[4]=1;
_isReduced1D := (nt) -> let(p := Length(nt.params[1].params), When(p=3, (Length(nt.params[1].params[1]) = 1), true));

_DetermineTag := function(t)
	local ll,tag,k;
	ll := When(Length(t.params)=3,Length(t.params[1]),1); #if MDDFT,x,1

	if(ll = 2 or ll = 1) then
		k := Int(Log2Int(t.getTag(1).rb)/2);
		k := 2^k;
		if(not (k^2 = t.getTag(1).rb)) then
			PrintLine("WARNING: Tile <-> Row-Buffer cannot be mathced perfectly!");
		fi;
		tag := ATile(k,t.getTag(1).m);
	else if(ll = 3) then
		k := Int(Log2Int(t.getTag(1).rb)/3);
		k := 2^k;
		if(not (k^3 = t.getTag(1).rb)) then
			PrintLine("WARNING: Cube <-> Row-Buffer cannot be mathced perfectly!");
		fi;
		tag := ACube(k,t.getTag(1).m);
	else
		PrintLine("I cannot handle an algorithm with ",ll," stages for now, sorry..");
		Error("");
	fi; fi;

	return tag;
end;
#  NewRulesFor(TCompose, rec(
#  # (AB) -> (A) fence (B)
#  	AB_tile := rec(
#  	)
#  ));

NewRulesFor(MDDFT, rec(
    MDDFT_tSPL_RowCol_break_2D := rec(
        info := "tSPL MDDFT_n -> MDDFT_n/d, MDDFT_d",

        applicable := (self, t) >> Length(t.params[1]) > 1 and not _isADramTag(t) and _isATileTag(t),
        freedoms := t -> [ [1..Length(t.params[1])-1] ],

        child := (t, fr) -> let(
            newdims := SplitAt(t.params[1], fr[1]),
            rot := t.params[2],
            [ TTensor(
                MDDFT(newdims[1], rot),
                MDDFT(newdims[2], rot)
            ).withTags(t.getTags())]
        ),

        apply := (t, C, Nonterms) -> C[1],
        switch := false
    ),

	MDDFT_tSPL_RowCol_break_3D := rec(
        info := "tSPL MDDFT_n -> MDDFT_n/d, MDDFT_d",

        applicable := (self, t) >> Length(t.params[1]) > 1 and not _isADramTag(t) and _isACubeTag(t),
        freedoms := t -> [ [1..Length(t.params[1])-1] ],

        children := (t) -> let(
            newdims := t.params[1],

            [[ TTensor3(
                MDDFT([newdims[1]]),
                MDDFT([newdims[2]]),
                MDDFT([newdims[3]])
            ).withTags(t.getTags())]]
        ),

        apply := (t, C, Nonterms) -> C[1],
        switch := false
    ),

	MDDFT_tSPL_RowCol_push := rec(
        info := "tSPL MDDFT_n -> MDDFT_n/d, MDDFT_d",

        applicable := (self, t) >> Length(t.params[1]) > 1 and _isADramTag(t),
        freedoms := t -> [ [1..Length(t.params[1])-1] ],

        child := (t, fr) -> let(
			tag := _DetermineTag(t),
			newTag := Concat([tag], Drop(t.getTags(), 1)),
            [ t.withoutFirstTag().withTags(newTag) ]
        ),

        apply := (t, C, Nonterms) -> C[1],
        switch := false
    )

));

NewRulesFor(TTwiddle, rec(
    TTwiddle_dram := rec(
        minSize := false,
        maxSize := false,
        applicable := (self, nt) >> ((IsBool(self.minSize) and not self.minSize) or nt.params[1] >= self.minSize) and
            ((IsBool(self.maxSize) and not self.maxSize) or nt.params[1] <= self.maxSize),
        forTransposition := false,
        apply := (t, C, Nonterms) -> TwiddleROM(t.params[1], t.params[2], t.params[3])
    )
));

#######################################################################################
#   tSPL DFT rules
NewRulesFor(DFT, rec(
    #F DFT_CT: 1965
    #F   General Cooley-Tukey Rule
    #F   DFT_n = (DFT_n/d tensor I_d) * diag * (I_n/d tensor F_d) * perm
    #F
    #F Cooley/Tukey:
    #F   An Algorithm for the Machine Calculation of Complex Fourier Series.
    #F   Mathematics of Computation, Vol. 19, 1965, pp. 297--301.
    #F
    DFT_tSPL_CT_tiled := rec(
    info          := "tSPL DFT(mn,k) -> DFT(m, k%m), DFT(n, k%n)",

    maxSize       := false,

    applicable    := (self, nt) >> nt.params[1] > 2
        and (self.maxSize = false or nt.params[1] <= self.maxSize)
		and _isATileTag(nt)
        and not IsPrime(nt.params[1])
		and IsPosInt(Sqrt(nt.params[1]))
        and nt.hasTags(),

    children      := nt -> let(n := Sqrt(nt.params[1]), m := n,

	[[
        TCompose([
            TGrp(TCompose([
                TTensorI(DFT(m, nt.params[2] mod m), n, AVec, AVec),
                TTwiddle(m*n, n, nt.params[2])
            ])),
            TGrp(TTensorI(DFT(n, nt.params[2] mod n), m, APar, AVec))
        ]).withTags(nt.getTags())
    ]]),

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

    switch := false
    ),

	DFT_tSPL_push_tiled := rec(
        info := "tSPL DFT(mn,k) -> DFT(m, k%m), DFT(n, k%n)",

		maxSize       := false,

		applicable := (self, t) >> t.params[1] > 2 and _isADramTag(t),

        children := (t) -> let(
			tag := _DetermineTag(t),
			newTag := Concat([tag], Drop(t.getTags(), 1)),
            [[ t.withoutFirstTag().withTags(newTag) ]]
        ),

        apply := (t, C, Nonterms) -> C[1],
        switch := false
    )
));

############################## 3D Rules ##############################

#   (A x B) rules
NewRulesFor(TTensor3, rec(
#   (A x B x C) -> (A x B x I)(I x I x C)
    AxBxI_IxIxC := rec(
        info := "(A x B x C) -> (A x B x I)(I x I x C)",
        forTransposition := false,
        applicable := nt -> true,
        #inplace := false,
        children := (self, nt) >> let(#inp := When(self.inplace, Inplace, x->x),
            [[ TCompose([
                TTensorI3(nt.params[1], nt.params[2], I(nt.params[3].dims()[2])),
                TTensorI3(I(nt.params[1].dims()[2]), I(nt.params[2].dims()[2]), nt.params[3])
            ]).withTags(nt.getTags()) ]]),
        apply := (nt, c, cnt) -> c[1],
#D        isApplicable := P -> true,
#D        allChildren := P -> [[TCompose([TTensorI(P[1], P[2].dims()[1], AVec, AVec), TTensorI(P[2], P[1].dims()[2], APar, APar)], P[3])]],
#D        rule := (P, C) -> C[1]
    ),
	#   (A x B x C) -> (A x I x I)(I x B x C)
    AxIxI_IxBxC := rec(
        info := "(A x B x C) -> (A x I x I)(I x B x C)",
        forTransposition := false,
        applicable := nt -> true,
        #inplace := false,
        children := (self, nt) >> let(#inp := When(self.inplace, Inplace, x->x),
            [[ TCompose([
				TTensorI3(nt.params[1], I(nt.params[2].dims()[2]), I(nt.params[3].dims()[2])),
				TTensorI3(I(nt.params[1].dims()[2]), nt.params[2], nt.params[3])
            ]).withTags(nt.getTags()) ]]),
        apply := (nt, c, cnt) -> c[1],
	),

	# (A x B x C) -> (A x I x I)(I x B x I)(I x I x C)
	AxIxI_IxBxI_IxIxC := rec(
        info := "(A x B x C) -> (A x I x I)(I x B x I)(I x I x C)",
        forTransposition := false,
        applicable := nt -> true,
        #inplace := false,
        children := (self, nt) >> let(#inp := When(self.inplace, Inplace, x->x),
            [[ TCompose([
				TTensorI3(nt.params[1], I(nt.params[2].dims()[2]), I(nt.params[3].dims()[2])),
				TTensorI3(I(nt.params[1].dims()[2]), nt.params[2], I(nt.params[3].dims()[2])),
				TTensorI3(I(nt.params[1].dims()[2]), I(nt.params[2].dims()[2]), nt.params[3])
            ]).withTags(nt.getTags()) ]]),
        apply := (nt, c, cnt) -> c[1],
	)
));

NewRulesFor(TTensorI3, rec(
	# (In x In x An) -> In3 (In2/k2 x (Ik x Ik x DFTn)) In3
	IxIxA_cube := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isACubeTag(nt)
							and IsIIA(nt.params)
							and IsPosInt(nt.params[3].dims()[2] / nt.getTag(1).k / nt.getTag(1).k)
							and (Length(nt.params[3].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.dims()[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[3].dims()[2],
			RdTag := Concat([ACubeRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ACubeWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(1,1,n*n*n,1).withTags(WrTag),
				TTensorI3(I(k),I(k),nt.params[3]).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(1,1,n*n*n,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[3].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n*n/k/k),
					(c[2])
				) *
				c[3]
			)
		)
	),

	# (An x In x In) -> Ln3_n (In2/k2 x (Ik x Ik x DFTn)) Ln3_n2
	AxIxI_cube := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isACubeTag(nt)
							and IsAII(nt.params)
							and IsPosInt(nt.params[1].dims()[2] / nt.getTag(1).k / nt.getTag(1).k)
							and (Length(nt.params[1].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.dims()[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],
			RdTag := Concat([ACubeRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ACubeWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(n*n*n,n,1,1).withTags(WrTag) ,
				TTensorI3(I(k),I(k),nt.params[1]).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(n*n*n,n*n,1,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n*n/k/k),
					(c[2])
				) *
				c[3]
			)
		)
	),

	# (In x An x In) -> In x Ln2_n (In2/k2 x (Ik x Ik x DFTn)) In x Ln2_n
	IxAxI_cube := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isACubeTag(nt)
							and IsIAI(nt.params)
							and IsPosInt(nt.params[2].dims()[2] / nt.getTag(1).k / nt.getTag(1).k)
							and (Length(nt.params[2].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.dims()[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[2].dims()[2],
			RdTag := Concat([ACubeRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ACubeWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(n*n,n,n,1).withTags(WrTag) ,
				TTensorI3(I(k),I(k),nt.params[2]).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(n*n,n,n,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[2].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n*n/k/k),
					(c[2])
				) *
				c[3]
			)
		)
	),

	IxIxA_cube_base := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isACubeTag(nt)
							and IsIIA(nt.params)
							and (Length(nt.params[3].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.dims()[2] <= nt.getTag(1).m),

		children := nt -> [[ ]],

		apply := (nt, c, cnt) -> let(
			n := nt.params[3].dims()[2],
			k2 := nt.params[2].dims()[2],
			k1 := nt.params[1].dims()[2],

			CompKern(I(k1),I(k2),DFT(n))
		)

	)

));

NewRulesFor(TL, rec(
# In^2_{read} -> (In/k x L^n_k x Ik)(In/k x L^n_n/k x Ik)
	I_cubeRd := rec(
		applicable := nt -> nt.hasTags()
							and _isACubeRdTag(nt)
							and _isTL_I(nt)
							and IsPosInt(RootInt(nt.params[3],3)) #n3 -> n
							and IsPosInt(RootInt(nt.params[3],3) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := RootInt(nt.params[3],3),
			#mem := MemRdPrm(fTensor(fId(n/k),L(n*n,n*n/k/k),fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n))),
			mem := MemRdPrm(fTensor(fId(n/k),L(n*n,n*n/k/k),fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n/k),fId(k))),
			loc := DTensor(I(n*n/k/k), LocalRdPrm(fTensor(L(n*k,k*k), fId(k)))),

			loc*mem

		)
	),
# In^2_{write} -> (In/k x L^n_k x Ik)(In/k x L^n_n/k x Ik)
	I_cubeWr := rec(
		applicable := nt -> nt.hasTags()
							and _isACubeWrTag(nt)
							and _isTL_I(nt)
							and IsPosInt(RootInt(nt.params[3],3)) #n3 -> n
							and IsPosInt(RootInt(nt.params[3],3) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := RootInt(nt.params[3],3),
			#mem := MemRdPrm(fTensor(fId(n/k),L(n*n,n*n/k/k),fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n))),
			mem := MemRdPrm(fTensor(fId(n/k),L(n*n,n*n/k/k),fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n/k),fId(k))),
			loc := DTensor(I(n*n/k/k), LocalRdPrm(fTensor(L(n*k,k*k), fId(k)))),

			TransposedSPL(loc*mem)
		)
	),
# Ln^2_n_{read} -> (In/k x L^nk_k)(L^n^2/k_n/k x Ik)
	Ln3n2_cubeRd := rec(
		applicable := nt -> nt.hasTags()
							and _isACubeRdTag(nt)
							and _isTL_L(nt)
							and IsPosInt(RootInt(nt.params[1],3)) #n3 -> n
							and IsPosInt(RootInt(nt.params[1],3) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := RootInt(nt.params[1],3),
			#mem := 	MemRdPrm(fTensor(L(n*n*n/k,n*n/k/k), fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n))),
			mem := 	MemRdPrm(fTensor(L(n*n*n/k,n*n/k/k), fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n/k),fId(k))),
			loc := DTensor(I(n*n/k/k), LocalRdPrm(fTensor(L(n*k*k,k*k)))),

			loc*mem
		)
	),
# Ln^2_n_{write} -> (L^n^2/k_n x Ik)(In/k x L^nk_n)
	Ln3n_cubeWr := rec(
		applicable := nt -> nt.hasTags()
							and _isACubeWrTag(nt)
							and _isTL_L(nt)
							and IsPosInt(RootInt(nt.params[1],3)) #n3 -> n
							and IsPosInt(RootInt(nt.params[1],3) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := RootInt(nt.params[1],3),
			#mem := 	MemRdPrm(fTensor(L(n*n*n/k,n*n/k/k), fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n))),
			mem := 	MemRdPrm(fTensor(L(n*n*n/k,n*n/k/k), fId(k))) * MemRdPrm(fTensor(fId(n),L(n,k),fId(n/k),fId(k))),
			loc := DTensor(I(n*n/k/k), LocalRdPrm(fTensor(L(n*k*k,k*k)))),

			TransposedSPL(loc*mem)
		)

	),
# Ln^2_n_{read} -> (In/k x L^nk_k)(L^n^2/k_n/k x Ik)
	InLn2n_cubeRd := rec(
		applicable := nt -> nt.hasTags()
							and _isACubeRdTag(nt)
							and _isTL_I_L(nt)
							and IsPosInt(RootInt(nt.params[1],2)) #n2 -> n
							and IsPosInt(RootInt(nt.params[1],2) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := RootInt(nt.params[1],2),
			#mem := MemRdPrm(fTensor(fId(n/k), L(n*n,n/k), fId(k))) * MemRdPrm(fTensor(fId(n/k), L(n,n/k), fId(n*k))),
			mem := MemRdPrm(fTensor(fId(n/k), L(n*n,n/k), fId(k))) * MemRdPrm(fTensor(fId(n/k), L(n,n/k), fId(n),fId(k))),
			loc := DTensor(I(n*n/k/k), LocalRdPrm(L(n*k*k,k*k))) * DTensor(I(n*n/k/k), LocalRdPrm(fTensor(fId(n/k),L(k*k,k),fId(k)))),

			loc*mem
		)
	),

	InLn2n_cubeWr := rec(
		applicable := nt -> nt.hasTags()
							and _isACubeWrTag(nt)
							and _isTL_I_L(nt)
							and IsPosInt(RootInt(nt.params[1],2)) #n2 -> n
							and IsPosInt(RootInt(nt.params[1],2) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := RootInt(nt.params[1],2),
			#mem := MemRdPrm(fTensor(fId(n/k), L(n*n,n/k), fId(k))) * MemRdPrm(fTensor(fId(n/k), L(n,n/k), fId(n*k))),
			mem := MemRdPrm(fTensor(fId(n/k), L(n*n,n/k), fId(k))) * MemRdPrm(fTensor(fId(n/k), L(n,n/k), fId(n),fId(k))),
			loc := DTensor(I(n*n/k/k), LocalRdPrm(L(n*k*k,k*k))) * DTensor(I(n*n/k/k), LocalRdPrm(fTensor(fId(n/k),L(k*k,k),fId(k)))),

			TransposedSPL(loc*mem)
		)
	)

));

############################## 2D Rules ##############################

NewRulesFor(TTensorI, rec(

# ================================================================
# nt.params; => [DFT_matix, Size_of_I, <APar,Avec>, <APar,Avec>]
# So,
# nt.params[1].dims()[2] -> Size of DFT matrix
# nt.params[2] -> Size of I matrix
# IsParPar actually checks nt.params[3]=APar and nt.params[4]=APar
#
# nt.getTag(1) -> Tag object. So access its methods via "."
#
# ================================================================
#  tSPL I x L x I
#  TL(m, n, k, j)
#  I(k), L(m, n), I(j)
# ================================================================

# (In x An) -> In^2 (In/k x Ik x An) In^2
# (In x An)_{Tile(k,m)} -> In^2_{TileWr(k,m)} (In/k x Ik x An) In^2_{TileRd(k,m)} for k|n and m>kn
	IxA_tile := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsParPar(nt.params)
							and IsPosInt(nt.params[1].dims()[2] / nt.getTag(1).k)
							and _isReduced1D(nt) # reduced to 1D-DFT kernel
							#and (Length(nt.params[1].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.params[1].dims()[2]*nt.params[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],
			RdTag := Concat([ATileRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ATileWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(1,1,n*n,1).withTags(WrTag) ,
				TTensorI(nt.params[1],k,APar,APar).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(1,1,n*n,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n/k),
					(c[2])
				) *
				c[3]
			)
		)
	),

# (An x In) -> L^{n^2}_n (In/k x Ik x An) L^{n^2}_n
	AxI_tile := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsVecVec(nt.params)
							and IsPosInt(nt.params[1].dims()[2] / nt.getTag(1).k)
							and _isReduced1D(nt) # reduced to 1D-DFT kernel
							#and (Length(nt.params[1].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.params[1].dims()[2]*nt.params[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],
			RdTag := Concat([ATileRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ATileWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(n*n,n,1,1).withTags(WrTag) ,
				TTensorI(nt.params[1],k,APar,APar).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(n*n,n,1,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n/k),
					(c[2])
				) *
				c[3]
			)
		)
	),
# L^{n^2}_n (In x An) -> L^{n^2}_n (In/k x Ik x An) In^2
	L_IxA_tile := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsVecPar(nt.params)
							and IsPosInt(nt.params[1].dims()[2] / nt.getTag(1).k)
							and _isReduced1D(nt) # reduced to 1D-DFT kernel
							#and (Length(nt.params[1].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.params[1].dims()[2]*nt.params[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],
			RdTag := Concat([ATileRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ATileWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(n*n,n,1,1).withTags(WrTag) ,
				TTensorI(nt.params[1],k,APar,APar).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(1,1,n*n,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n/k),
					(c[2])
				) *
				c[3]
			)
		)
	),
# (In x An) L^{n^2}_n -> In^2 (In/k x Ik x An) L^{n^2}_n
	IxA_L_tile := rec(
		forTransposition := false,
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsParVec(nt.params)
							and IsPosInt(nt.params[1].dims()[2] / nt.getTag(1).k)
							and _isReduced1D(nt) # reduced to 1D-DFT kernel
							#and (Length(nt.params[1].params[1]) = 1) # reduced to 1D-DFT kernel
							and (nt.params[1].dims()[2]*nt.params[2] > nt.getTag(1).m),

		children := nt -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],
			RdTag := Concat([ATileRd(k,m)], Drop(nt.getTags(), 1)),
			WrTag := Concat([ATileWr(k,m)], Drop(nt.getTags(), 1)),

			[[
				TL(1,1,n*n,1).withTags(WrTag) ,
				TTensorI(nt.params[1],k,APar,APar).withTags(nt.getTags()), #.withTags(Drop(nt.getTags(), 1)) ,
				TL(n*n,n,1,1).withTags(RdTag)
			]]
		),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := nt.params[1].dims()[2],

			MemFence(
				c[1] *
				DTensor(
					I(n/k),
					(c[2])
				) *
				c[3]
			)
		)
	)
));

# Termination Rules, i.e. computation fits in local memory
NewRulesFor(TTensorI, rec(
	IxA_tile_base := rec(
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsParPar(nt.params)
							and (nt.params[1].dims()[2] * nt.params[2] <= nt.getTag(1).m),

		children := nt -> [[ ]],

		apply := (nt, c ,cnt) -> let(
			k := nt.params[2],
			n := nt.params[1].dims()[2],

			CompKern(I(k), DFT(n))
		)
	),

	AxI_tile_base := rec(
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsVecVec(nt.params)
							and (nt.params[1].dims()[2] * nt.params[2] <= nt.getTag(1).m),

		children := nt -> [[ ]],

		apply := (nt, c ,cnt) -> let(
			k := nt.params[2],
			n := nt.params[1].dims()[2],

			# NOTE: Check the L index!
			LocalWrPrm(fTensor(L(k*n,n))) * CompKern(I(k), DFT(n)) * LocalRdPrm(fTensor(L(n*k,k)))
			# TPrm and TL for splhdl
			#LocalWrPrm(Tensor(L(k*n,n))) * CompKern(I(k), DFT(n)) * LocalRdPrm(Tensor(L(n*k,k)))
		)
	),

	L_IxA_tile_base := rec(
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsVecPar(nt.params)
							and (nt.params[1].dims()[2] * nt.params[2] <= nt.getTag(1).m),

		children := nt -> [[ ]],

		apply := (nt, c ,cnt) -> let(
			k := nt.params[2],
			n := nt.params[1].dims()[2],

			# NOTE: Check the L index!
			# TTensorI(DFTn,k,AVec,APar) -> L(n*k,???) * (Ik x DFTn)
			#
			#
			#
			LocalWrPrm(fTensor(L(n*k,k))) * CompKern(I(k),DFT(n))
			# TPrm and TL for splhdl
			#LocalWrPrm(Tensor(L(n*k,k))) * CompKern(I(k),DFT(n))
		)
	),

	IxA_L_tile_base := rec(
		applicable := nt -> nt.hasTags()
							and _isATileTag(nt)
							and IsParVec(nt.params)
							and (nt.params[1].dims()[2] * nt.params[2] <= nt.getTag(1).m),

		children := nt -> [[ ]],

		apply := (nt, c ,cnt) -> let(
			k := nt.params[2],
			n := nt.params[1].dims()[2],

			# NOTE: Check the L index!
			CompKern(I(k),DFT(n)) * LocalRdPrm(fTensor(L(k*n,n)))
			# TPrm and TL for splhdl
			#CompKern(I(k),DFT(n)) * LocalRdPrm(Tensor(L(k*n,n)))
		)
	)
));

NewRulesFor(TL, rec(
# In^2_{read} -> (In/k x L^n_k x Ik)(In/k x L^n_n/k x Ik)
	I_tileRd := rec(
		applicable := nt -> nt.hasTags()
							and _isATileRdTag(nt)
							and _isTL_I(nt)
							and IsPosInt(Sqrt(nt.params[3]))
							and IsPosInt(Sqrt(nt.params[3]) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := Sqrt(nt.params[3]),

			DTensor(I(n/k), LocalRdPrm(fTensor(L(n,k),fId(k)))) * MemRdPrm(fTensor(fId(n/k), L(n,n/k), fId(k)))
			# TPrm and TL for splhdl
			#DTensor(I(n/k), LocalRdPrm(Tensor(L(n,k),I(k)))) * MemRdPrm(fTensor(fId(n/k), L(n,n/k), fId(k)))

		)
	),
# In^2_{write} -> (In/k x L^n_k x Ik)(In/k x L^n_n/k x Ik)
	I_tileWr := rec(
		applicable := nt -> nt.hasTags()
							and _isATileWrTag(nt)
							and _isTL_I(nt)
							and IsPosInt(Sqrt(nt.params[3]))
							and IsPosInt(Sqrt(nt.params[3]) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := Sqrt(nt.params[3]),

			MemWrPrm(fTensor(fId(n/k), L(n,k), fId(k))) * DTensor(I(n/k), LocalWrPrm(fTensor(L(n,n/k),fId(k))))
			# TPrm and TL for splhdl
			#MemWrPrm(fTensor(fId(n/k), L(n,k), fId(k))) * DTensor(I(n/k), LocalWrPrm(Tensor(L(n,n/k),I(k))))
		)
	),
# Ln^2_n_{read} -> (In/k x L^nk_k)(L^n^2/k_n/k x Ik)
	L_tileRd := rec(
		applicable := nt -> nt.hasTags()
							and _isATileRdTag(nt)
							and _isTL_L(nt)
							and IsPosInt(Sqrt(nt.params[1]))
							and IsPosInt(Sqrt(nt.params[1]) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := Sqrt(nt.params[1]),

			DTensor(I(n/k), LocalRdPrm(fTensor(L(n*k,k)))) * MemRdPrm(fTensor(L(n*n/k,n/k), fId(k)))
			# TPrm and TL for splhdl
			#DTensor(I(n/k), LocalRdPrm(Tensor(L(n*k,k)))) * MemRdPrm(fTensor(L(n*n/k,n/k), fId(k)))

		)
	),
# Ln^2_n_{write} -> (L^n^2/k_n x Ik)(In/k x L^nk_n)
	L_tileWr := rec(
		applicable := nt -> nt.hasTags()
							and _isATileWrTag(nt)
							and _isTL_L(nt)
							and IsPosInt(Sqrt(nt.params[1]))
							and IsPosInt(Sqrt(nt.params[1]) / nt.getTag(1).k),

		apply := (nt, c, cnt) -> let(
			k := nt.getTag(1).k,
			m := nt.getTag(1).m,
			n := Sqrt(nt.params[1]),

			MemWrPrm(fTensor(L(n*n/k,n), fId(k))) * DTensor(I(n/k), LocalWrPrm(fTensor(L(n*k,n))))
			# TPrm and TL for splhdl
			#MemWrPrm(fTensor(L(n*n/k,n), fId(k))) * DTensor(I(n/k), LocalWrPrm(Tensor(L(n*k,n))))
		)

	)
));

#==============================================================
# First version of TL rules
# Generates children for permutations that is to be further optimized
#==============================================================
#	NewRulesFor(TL, rec(
#	# In^2_{read} -> (In/k x L^n_k x Ik)(In/k x L^n_n/k x Ik)
#		I_tileRd := rec(
#			applicable := nt -> nt.hasTags()
#								and _isATileRdTag(nt)
#								and _isTL_I(nt)
#								and IsPosInt(Sqrt(nt.params[3]))
#								and IsPosInt(Sqrt(nt.params[3]) / nt.getTag(1).k),
#
#			children := nt -> let(
#				k := nt.getTag(1).k,
#				m := nt.getTag(1).m,
#				n := Sqrt(nt.params[3]),
#
#				[[
#					TL(n, k, n/k, k).withTags(Drop(nt.getTags(), 1)) ,
#					TL(n, n/k, n/k, k).withTags(Drop(nt.getTags(), 1))
#				]]
#			),
#
#			apply := (nt, c, cnt) -> c[1] * c[2]
#		),
#	# In^2_{write} -> (In/k x L^n_k x Ik)(In/k x L^n_n/k x Ik)
#		I_tileWr := rec(
#			applicable := nt -> nt.hasTags()
#								and _isATileWrTag(nt)
#								and _isTL_I(nt)
#								and IsPosInt(Sqrt(nt.params[3]))
#								and IsPosInt(Sqrt(nt.params[3]) / nt.getTag(1).k),
#
#			children := nt -> let(
#				k := nt.getTag(1).k,
#				m := nt.getTag(1).m,
#				n := Sqrt(nt.params[3]),
#
#				[[
#					TL(n, k, n/k, k).withTags(Drop(nt.getTags(), 1)) ,
#					TL(n, n/k, n/k, k).withTags(Drop(nt.getTags(), 1))
#				]]
#			),
#
#			apply := (nt, c, cnt) -> c[1] * c[2]
#		),
#	# Ln^2_n_{read} -> (In/k x L^nk_k)(L^n^2/k_n/k x Ik)
#		L_tileRd := rec(
#			applicable := nt -> nt.hasTags()
#								and _isATileRdTag(nt)
#								and _isTL_L(nt)
#								and IsPosInt(Sqrt(nt.params[1]))
#								and IsPosInt(Sqrt(nt.params[1]) / nt.getTag(1).k),
#
#			children := nt -> let(
#				k := nt.getTag(1).k,
#				m := nt.getTag(1).m,
#				n := Sqrt(nt.params[1]),
#
#				[[
#					TL(n*k, k, n/k, 1).withTags(Drop(nt.getTags(), 1)) ,
#					TL(n*n/k, n/k, 1, k).withTags(Drop(nt.getTags(), 1))
#				]]
#			),
#
#			apply := (nt, c, cnt) -> c[1] * c[2]
#		),
#	# Ln^2_n_{write} -> (L^n^2/k_n x Ik)(In/k x L^nk_n)
#		L_tileWr := rec(
#			applicable := nt -> nt.hasTags()
#								and _isATileWrTag(nt)
#								and _isTL_L(nt)
#								and IsPosInt(Sqrt(nt.params[1]))
#								and IsPosInt(Sqrt(nt.params[1]) / nt.getTag(1).k),
#
#			children := nt -> let(
#				k := nt.getTag(1).k,
#				m := nt.getTag(1).m,
#				n := Sqrt(nt.params[1]),
#
#				[[
#					TL(n*n/k, n, 1, k).withTags(Drop(nt.getTags(), 1)),
#					TL(n*k, n, n/k, 1).withTags(Drop(nt.getTags(), 1))
#				]]
#			),
#
#			apply := (nt, c, cnt) -> c[1] * c[2]
#
#		)
#	));
#


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

#---------------------------------------------------------------------------------------
#   altivec vector instructions
#---------------------------------------------------------------------------------------

#Class(vbinop_4x32f_av, vbinop_av, rec( v := self >> 4));

#Class(exp_4x32f, rec(v := 4, computeType := self >> TVect(TDouble, 4)));

#Class(cmd_4x32f, rec(v := 4, computeType := self >> TVect(TDouble, 4)));

# Load -----------------------------

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

# Zero -----------------------------
# use data_type.zero() instead of special class
# Class(vzero_4x32f, vop_av, exp_4x32f, rec(numargs := 0));

# Subvec ---------------------------

# Binary ---------------------------
# Class(bin_or, vbinop_new, exp_4x32f);

# Rotates ---------------------------

# Binary shuffle -------------------

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

# Unary shuffle --------------------
#Class(vuperm_4x32f_spu, vunbinop_spu, rec(binop := vperm_4x32f_spu));


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

Class(spu_common, SIMD_VMX, rec(
    file := "cell",
    unparser := CellUnparser,
    #compileStrategy := self >> IndicesCS2_FMA,
    compileStrategy := self >> IndicesCS2,
    useDeref := true,

    vadd    := "spu_add",
    vsub    := "spu_sub",
    vmul    := "spu_mul",
    vlshift := "spu_sl",
    vrshift := "spu_sr",
    vbinand := "spu_and",

    autolib := rec(
       #includes := () -> ["<vec_types.h>", "<altivec.h>", "<spu2vmx.h>" ], # This is for PowerPC (PPE)
       includes := () -> ["<spu_intrinsics.h>", "<include/sp_general_malloc.h>",
                   "<simdmath.h>", "<simdmath/negated2.h>", "<simdmath/negatef4.h>"],
       timerIncludes := () -> ["<include/sp_spe_timer.h>" ],
    ),

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

Class(spu_4x32f, spu_common, rec(
    active := true,
    isFixedPoint := false,
    info := "SPU 4 x 32-bit float",
    v := 4,
    t := TVect(TReal, 4),
    globalUnrolling := 256,
    ctype := "float",
    vtype := "vector float",
    stype := "__attribute__ ((aligned(16))) float",
    instr := [ vperm_4x32f_spu, vuperm_4x32f_spu ],
    bits := 32,
    isFloat := true,
    isFix := false,
    includes := () -> ["<spu_intrinsics.h>", "<omega32.h>", "<simdmath.h>", "<simdmath/negated2.h>", "<simdmath/negatef4.h>"],
    infix_op := false,
    infix_assign := true,
    vmuladd := false,
    dupload := (y, x) -> assign(y, vuperm_4x32f_spu(promote_spu4x32f(x, 0), [1,1,1,1])),
    #duploadn takes a vector and replicates scalar number n in a vector
    duploadn := (y, x, n) -> assign(y, vuperm_4x32f_spu(x, [n,n,n,n])),
    vzero   := vzero_4x32f,
    vconst := vconstpr_av,
    vconstv := "(vector float)",
    vconst1 := "(vector float)spu_splats",
    splopts := rec(precision := "single"),
    countrec := rec( ops := [[add, sub], [mul], [fma, fms, nfma], [vperm_4x32f_spu]],
                     printstrings := ["[adds]", "[mults]", "[fmas]", "[vperms]"],
                     type := "TVect",
                     arithcost := (self, opcount) >> opcount[1]+opcount[2]+(2*opcount[3])
                     ),

    #NOTE:
    # - If operating on a NAN has no penalty, we shouldn't bother promoting and
    # shuffling to zero, for instance, where we could simply load directly.

    #NOTE:
    # - Find out if these are the fastest/most efficient (lowest register
    #   usage). For instance, how does a
    #   (promote,promote,perm,promote,promote,perm,perm) compare to a (promote,promote,perm,insert,insert)?
    # - Also, should we use temp variables here or not? (might help compiler propagation passes?)
    # - How to reuse loadc code here?

    #----------------------------------------
    #NOTE: Find out if ObjId(x)=nth should hold for all conditions. Doesn't seem to.
    #   (y,x) -> Checked(ObjId(x)=nth, assign(y, vperm_4x32f_spu(promote_spu4x32f(x, 0), vzero_spu(), [1, 128, 128, 128]))),
    svload := [[ # load using subvectors of length 1
              (y,x,opts) -> assign(y, vuperm_4x32f_spu(promote_spu4x32f(x[1], 0), [1, 128, 128, 128])),
              (y,x,opts) -> assign(y, vperm_4x32f_spu(promote_spu4x32f(x[1], 0), promote_spu4x32f(x[2], 0), [1,   5, 128, 128])),
              (y,x,opts) -> assign(y, insert_spu4x32f(x[3], vperm_4x32f_spu(promote_spu4x32f(x[1], 0), promote_spu4x32f(x[2], 0), [1,   5, 128, 128]), 2)),
              (y,x,opts) -> assign(y, insert_spu4x32f(x[4], insert_spu4x32f(x[3], insert_spu4x32f(x[2], promote_spu4x32f(x[1], 0), 1), 2), 3) ),
              ],
              [ # load using subvectors of length 2
              (y,x,opts) -> assign(y, vperm_4x32f_spu(promote_spu4x32f(x[1], 0), promote_spu4x32f(nth(x[1].loc, x[1].idx+1), 0), [1, 5, 128, 128])),
              (y,x,opts) -> assign(y, insert_spu4x32f(nth(x[2].loc, x[2].idx+1), insert_spu4x32f(x[2], insert_spu4x32f(nth(x[1].loc, x[1].idx+1), promote_spu4x32f(x[1], 0), 1), 2), 3) ),
              ]],
    #----------------------------------------
    #NOTE: loadu3 and loadu4 need work, prolly mostly in the unparser.
#     loadc := [      # load contiguous unaligned
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_4x32f_spu(promote_spu4x32f(x, 0), [1, 128, 128, 128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vperm_4x32f_spu(promote_spu4x32f(x, 0), promote_spu4x32f(nth(x.loc, x.idx+1), 0), [1, 5, 128, 128]))),

# #NOTE: This might be the best way to do it. Need to get it to work (past the ptr, ptr+1 problems).
# #(y,x) -> assign(y, vperm_4x32f_spu(bin_or(slqwbyte_spu4x32f(x.toPtr(TVect(TDouble, 4))), rlmaskqwbyte_spu4x32f(x.toPtr(TVect(TDouble, 4)))), vzero_spu(), [1,2,3,128])),
# #(y,x) -> assign(y, bin_or(slqwbyte_spu4x32f(x.toPtr(TVect(TDouble, 4))), rlmaskqwbyte_spu4x32f(x.toPtr(TVect(TDouble, 4))))),

# #NOTE: This is kind of a go-between for now. This bad because it's hacked via the unparser, but good because it only does 2 mem references
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_4x32f_spu(vloadu4_spu4x32f(x), [1, 2, 3, 128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vloadu4_spu4x32f(x)))
#     ],

###########################################################################
###### Vas, please check
###########################################################################

    # keep the n lower scalars and zero the other ones
    optional_mask :=  (c, n, opts) -> When(IsBound(opts.trueSVSemantics) and opts.trueSVSemantics and not(n=4),
        vuperm_4x32f_spu(c, List([1..4],x->When(x<=n, x, 128))),
        c),

    # load contiguous with unaligned loads
    loadc := (self, sv, opts) >> Cond(sv=1,
        ((y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_4x32f_spu(promote_spu4x32f(x, 0), [1, 128, 128, 128])))),
        sv=2,
        (y,x) -> Checked(ObjId(x)=nth, assign(y, vperm_4x32f_spu(promote_spu4x32f(x, 0), promote_spu4x32f(nth(x.loc, x.idx+1), 0), [1, 5, 128, 128]))),
#NOTE: loadu3 and loadu4 need work, prolly mostly in the unparser.
#NOTE: This is kind of a go-between for now. This bad because it's hacked via the unparser, but good because it only does 2 mem references
        sv=3,
        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_4x32f_spu(vloadu4_spu4x32f(x), [1, 2, 3, 128]))),
        sv=4,
        (y,x) -> Checked(ObjId(x)=nth, assign(y, vloadu4_spu4x32f(x))),
        Error("bad arguments for loadc")),

    #load contiguous + known alignment -> using 2 aligned load to be smarter
    loadc_align := (self, sv, align, opts)>>
    ((y,x,addr) -> let(
        v1 := nth(nth(x,add(addr,-align)).toPtr(TVect(TReal, 4)),0),
        v2 := nth(nth(x,add(addr,4-align)).toPtr(TVect(TReal, 4)), 0),
        m := x-> self.optional_mask(x, sv, opts),
        Cond(align=0,
            assign(y, m(v1)),
            assign(y, vperm_4x32f_spu(v1, v2, List([1..4], x-> When(x<=sv, align+x, 128))))
            ))),

###########################################################################
###### </Vas>
###########################################################################

    #----------------------------------------
    svstore := [[ # store using subvectors of length 1
            (y,x,opts) ->        assign(y[1], extract_spu4x32f(x, 0)),

            (y,x,opts) -> chain( assign(y[1], extract_spu4x32f(x, 0)),
                            assign(y[2], extract_spu4x32f(x, 1)) ),

            (y,x,opts) -> chain( assign(y[1], extract_spu4x32f(x, 0)),
                            assign(y[2], extract_spu4x32f(x, 1)),
                            assign(y[3], extract_spu4x32f(x, 2)) ),

            (y,x,opts) -> chain( assign(y[1], extract_spu4x32f(x, 0)),
                            assign(y[2], extract_spu4x32f(x, 1)),
                            assign(y[3], extract_spu4x32f(x, 2)),
                            assign(y[4], extract_spu4x32f(x, 3)) ),
              ],

              [ # store using subvectors of length 2
            (y,x,opts) -> chain( assign(y[1],                      extract_spu4x32f(x, 0)),
                            assign(nth(y[1].loc, y[1].idx+1), extract_spu4x32f(x, 1)) ),

            (y,x,opts) -> chain( assign(y[1],                      extract_spu4x32f(x, 0)),
                            assign(nth(y[1].loc, y[1].idx+1), extract_spu4x32f(x, 1)),
                            assign(y[2],                      extract_spu4x32f(x, 2)),
                            assign(nth(y[2].loc, y[2].idx+1), extract_spu4x32f(x, 3)) ),
              ]],
    #----------------------------------------
    storec := [    # store contiguous unaligned
            (y,x) ->        assign(y, extract_spu4x32f(x, 0)),
            (y,x) -> chain( assign(y,                   extract_spu4x32f(x, 0)),
                            assign(nth(y.loc, y.idx+1), extract_spu4x32f(x, 1)) ),
            (y,x) -> chain( assign(y,                   extract_spu4x32f(x, 0)),
                            assign(nth(y.loc, y.idx+1), extract_spu4x32f(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu4x32f(x, 2)) ),
            (y,x) -> chain( assign(y,                   extract_spu4x32f(x, 0)),
                            assign(nth(y.loc, y.idx+1), extract_spu4x32f(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu4x32f(x, 2)),
                            assign(nth(y.loc, y.idx+3), extract_spu4x32f(x, 3)) ),
        ],
    reverse := (y,x) -> assign(vref(y,0,4), vuperm_4x32f_spu(vref(x,0,4), [4, 3, 2, 1])),

    #----------------------------------------
    # support for VS and VS.transpose()

    shl1 :=  (y,x,opts) -> assign(y, vuperm_4x32f_spu(x, [128,1,2,3])),
    shl2 :=  (y,x,opts) -> assign(y, vperm_4x32f_spu(x[1], x[2], [4,5,6,7])),
    shr1 :=  (y,x,opts) -> assign(y, vuperm_4x32f_spu(x, [2, 3, 4, 128])),
    shr2 :=  (y,x,opts) -> assign(y, vperm_4x32f_spu(x[1], x[2], [2,3,4,5])),

    # support for VO1dsJ(n, v)
    shrev := (y,x,opts) -> assign(y, vperm_4x32f_spu(x[1], x[2], [1, 8, 7, 6])),

    swap_cx := (y, x, opts) -> assign(y, vuperm_2x64f_spu(x, [2,1,4,3])),
    RCVIxJ2 := (y, x, opts) -> assign(y, vuperm_2x64f_spu(x, [2,1,4,3]))

));

Class(spu_2x64f, spu_common, rec(
    active  := true,
    isFixedPoint := false,
    info    := "SPU 2 x 64-bit double",
    v       := 2,
    t := TVect(TReal, 2),
    globalUnrolling := 256,
    ctype   := "double",
    vtype   := "vector double",
    stype   := "__attribute__ ((aligned(16))) double",
    instr   := [ vperm_2x64f_spu, vuperm_2x64f_spu ],
    bits    := 64,
    isFloat := true,
    isFix := false,
    includes := () -> ["<spu_intrinsics.h>", "<omega32.h>"],
    infix_op := false,
    infix_assign := true,
    vmuladd := false,

    #NOTE: fix these
    #dupload := (y, x) -> assign(y, vuperm_4x32f_spu(promote_spu4x32f(x, 0), [1,1,1,1])),
    #duploadn takes a vector and replicates scalar number n in a vector
    #uploadn := (y, x, n) -> assign(y, vuperm_4x32f_spu(x, [n,n,n,n])),
    vzero   := vzero_2x64f,

    vconstv := "(vector double)",
    vconst1 := "(vector double)spu_splats",
    splopts := rec(precision := "double"),

    #NOTE: fix these
    #upload := (y, x) -> assign(y, vperm_2x64f_spu(promote_spu2x64f(x, 0), vzero_2x64f(), [1,1])),
    #uploadn := (y, x, n) -> assign(y, vuperm_2x64f_spu(x, [n,n])),

    # NOTE: Check if each of these are correct/efficient.
    svload := [[ # load using subvectors of length 1
              (y,x,opts) -> assign(y, vuperm_2x64f_spu(promote_spu2x64f(x[1], 0), [1, 128])),
              (y,x,opts) -> assign(y, vperm_2x64f_spu(promote_spu2x64f(x[1], 0), promote_spu2x64f(x[2], 0), [1,   3])),
              ],
              [ # load using subvectors of length 2
              (y,x,opts) -> assign(y, vperm_2x64f_spu(promote_spu2x64f(x[1], 0), promote_spu2x64f(nth(x[1].loc, x[1].idx+1), 0), [1, 3])),
              ]],

#     loadc := [      # load contiguous unaligned
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_2x64f_spu(promote_spu2x64f(x, 0), [1, 128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vperm_2x64f_spu(promote_spu2x64f(x, 0), promote_spu2x64f(nth(x.loc, x.idx+1), 0), [1, 3]))),
#        ],

###########################################################################
###### NOTE: Vas, please check
###########################################################################

    # keep the n lower scalars and zero the other ones
    optional_mask :=  (c, n, opts) -> When(IsBound(opts.trueSVSemantics) and opts.trueSVSemantics and not(n=2),
        vuperm_2x64f_spu(c, List([1..2],x->When(x<=n, x, 128))),
        c),

    # load contiguous with unaligned loads
    loadc := (self, sv, opts) >> Cond(sv=1,
        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_2x64f_spu(promote_spu2x64f(x, 0), [1, 128]))),
        sv=2,
        (y,x) -> Checked(ObjId(x)=nth, assign(y, vperm_2x64f_spu(promote_spu2x64f(x, 0), promote_spu2x64f(nth(x.loc, x.idx+1), 0), [1, 3]))),
        Error("bad arguments for loadc")),

    #load contiguous + known alignment -> using 2 aligned load to be smarter
    loadc_align := (self, sv, align, opts)>>
    ((y,x,addr) -> let(
        v1 := nth(nth(x,add(addr,-align)).toPtr(TVect(TReal, 2)),0),
        v2 := nth(nth(x,add(addr,2-align)).toPtr(TVect(TReal, 2)), 0),
        m := x-> self.optional_mask(x, sv, opts),
        Cond(align=0,
            assign(y, m(v1)),
            assign(y, vperm_2x64f_spu(v1, v2, List([1..2], x-> When(x<=sv, align+x, 128))))
            ))),

###########################################################################
###### </Vas>
###########################################################################

    svstore := [[ # store using subvectors of length 1
               (y,x,opts) ->        assign(y[1], extract_spu2x64f(x, 0)),

               (y,x,opts) -> chain( assign(y[1], extract_spu2x64f(x, 0)),
                               assign(y[2], extract_spu2x64f(x, 1)) ),
                ],
                [ # store using subvectors of length 2
                (y,x,opts) -> chain( assign(y[1],                      extract_spu2x64f(x, 0)),
                                assign(nth(y[1].loc, y[1].idx+1), extract_spu2x64f(x, 1)) ),
               ]],

    storec := [    # store contiguous unaligned
              (y,x) ->        assign(y, extract_spu2x64f(x, 0)),
              (y,x) -> chain( assign(y,                   extract_spu2x64f(x, 0)),
                              assign(nth(y.loc, y.idx+1), extract_spu2x64f(x, 1)) ),
              ],
    reverse := (y,x) -> assign(vref(y,0,2), vuperm_2x64f_spu(vref(x,0,2), [2, 1])),

    # Complex multiplication
    mul_cx := (self, opts) >> Cond(
               opts.vector.SIMD in ["SSE"],
               Error("opts.vector.SIMD is in SSE, while I am in SPU. You've got problems!"),
               (y, x, c) ->
                    let(u1 := var.fresh_t("U", TVectDouble(2)), u2 := var.fresh_t("U", TVectDouble(2)),
                        u3 := var.fresh_t("U", TVectDouble(2)), u4 := var.fresh_t("U", TVectDouble(2)),
                        decl([u1, u2, u3, u4], chain(
                            assign(u1, mul(x, vuperm_2x64f_spu(c, [1,1]))),
                            assign(u2, vuperm_2x64f_spu(x, [2,1])),
                            assign(u3, mul(u2, vuperm_2x64f_spu(c, [2,2]))),
                            assign(u4, vuperm_2x64f_spu(u3, [2,1])),
                            assign(y, add(u1, u4)))))
                ),

    #----------------------------------------
    # support for VS and VS.transpose()

    shl1 :=  (y,x,opts) -> assign(y, vperm_2x64f_spu(x[1], x[2], [2, 3])),
    shl2 :=  (y,x,opts) -> assign(y, vperm_2x64f_spu(x[1], x[2], [2, 128])),
    shr1 :=  (y,x,opts) -> assign(y, vperm_2x64f_spu(x[1], x[2], [4, 1])),
    shr2 :=  (y,x,opts) -> assign(y, vperm_2x64f_spu(x[1], x[2], [128, 1])),

    # support for VO1dsJ(n, v)
    shrev := (y,x,opts) -> assign(y, vperm_2x64f_spu(x[1], x[2], [1, 2])),

    swap_cx := (y, x, opts) -> assign(y, vuperm_2x64f_spu(x, [2,1])),
    RCVIxJ2 := (y, x, opts) -> assign(y, vuperm_2x64f_spu(x, [2,1]))

));

Class(ppu_4x32f, spu_4x32f, rec(
    includes := () -> ["<omega32.h>", "<vec_types.h>", "<altivec.h>", "<spu2vmx.h>"],
    backendConfig := rec(
                        profile := default_profiles.linux_cellPPU_gcc,
                        measureFunction := _StandardMeasureVerify
                     )
));

Class(spu_8x16i, spu_common, rec(
    active          := false,
    isFixedPoint    := true,
    saturatedArithmetic := true,
    info            := "SPU 8 x 16-bit int",
    v               := 8,
    ctype           := "short int",
    vtype           := "vector signed short",
    stype           := "__attribute__ ((aligned(16))) short int",
    # NOTE: What is svtype?
    svtype          := ["short int", "int", "signed long long", "signed long long"],
    instr           := [ vperm_8x16i_spu, vuperm_8x16i_spu ],
    bits            := 16,
    isFloat         := false,
    isFix           := true,
    includes        := () -> ["<include/omega16i.h>", "<include/mm_malloc.h>", "<spu_intrinsics.h>"],
    infix_op        := false,
    infix_assign    := true,
    vzero           := vzero_8x16i,
    vconstv         := "(vector signed short)",
    vconst1         := "(vector signed short)spu_splats",
    #NOTE: What is vconsthex? - it's to set 8 values. Value in unparser should take care of it?
    #vconsthex       := p->Chain(Print("_mm_set_epi16("), PrintCS(p), Print(")")),
    #NOTE: what are the following two?
    #vconst1init     := c -> PrintCS(List([1..8], i->c)),
    #vconst          := vconstprfp,
    splopts         := rec(customDataType := "signed short"),

    #NOTE: These REALLY need to be rewritten. They're correct, but end up doing
    #extra loads, shifts, rotates like nobody's business.
#     loadc := [      # load contiguous unaligned
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_8x16i_spu(promote_spu8x16i(x, 0), [1, 128, 128, 128, 128, 128, 128, 128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vperm_8x16i_spu(promote_spu8x16i(x, 0), promote_spu8x16i(nth(x.loc, x.idx+1), 0), [1,5,128,128,128,128,128,128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_8x16i_spu(vloadu8_spu8x16i(x), [1,2,3,128,128,128,128,128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_8x16i_spu(vloadu8_spu8x16i(x), [1,2,3,  4,128,128,128,128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_8x16i_spu(vloadu8_spu8x16i(x), [1,2,3,  4,  5,128,128,128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_8x16i_spu(vloadu8_spu8x16i(x), [1,2,3,  4,  5,  6,128,128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vuperm_8x16i_spu(vloadu8_spu8x16i(x), [1,2,3,  4,  5,  6,  7,128]))),
#        (y,x) -> Checked(ObjId(x)=nth, assign(y, vloadu8_spu8x16i(x)))
#     ],

###########################################################################
###### Vas, please check
###########################################################################

    # keep the n lower scalars and zero the other ones
    optional_mask :=  (c, n, opts) -> When(IsBound(opts.trueSVSemantics) and opts.trueSVSemantics and not(n=8),
        vuperm_8x16i_spu(c, List([1..8],x->When(x<=n, x, 128))),
        c),

    #NOTE: These REALLY need to be rewritten. They're correct, but end up doing
    #extra loads, shifts, rotates like nobody's business.
    # load contiguous with unaligned loads
    loadc := (self, sv, opts) >> ((y,x) -> assign(y, self.optional_mask(vloadu8_spu8x16i(x.toPtr(TVect(TReal, 8))), sv, opts))),

    #load contiguous + known alignment -> using 2 aligned load to be smarter
    loadc_align := (self, sv, align, opts)>>
    ((y,x,addr) -> let(
        v1 := nth(nth(x,add(addr,-align)).toPtr(TVect(TReal, 8)),0),
        v2 := nth(nth(x,add(addr,8-align)).toPtr(TVect(TReal, 8)), 0),
        m := x-> self.optional_mask(x, sv, opts),
        Cond(align=0,
            assign(y, m(v1)),
            assign(y, vperm_8x16i_spu(v1, v2, List([1..8], x-> When(x<=sv, align+x, 128))))
            ))),

###########################################################################
###### </Vas>
###########################################################################

    #NOTE: See if there's a better way to do this. Also look at assembly to see how good/bad this is.
    storec := [    # store contiguous unaligned
            (y,x) ->        assign(y, extract_spu8x16i(x, 0)),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1))),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu8x16i(x, 2))),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+3), extract_spu8x16i(x, 3))),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+3), extract_spu8x16i(x, 3)),
                            assign(nth(y.loc, y.idx+4), extract_spu8x16i(x, 2))),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+3), extract_spu8x16i(x, 3)),
                            assign(nth(y.loc, y.idx+4), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+5), extract_spu8x16i(x, 5))),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+3), extract_spu8x16i(x, 3)),
                            assign(nth(y.loc, y.idx+4), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+5), extract_spu8x16i(x, 5)),
                            assign(nth(y.loc, y.idx+6), extract_spu8x16i(x, 2))),

            (y,x) -> chain( assign(y,                   extract_spu8x16i(x, 0)), assign(nth(y.loc, y.idx+1), extract_spu8x16i(x, 1)),
                            assign(nth(y.loc, y.idx+2), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+3), extract_spu8x16i(x, 3)),
                            assign(nth(y.loc, y.idx+4), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+5), extract_spu8x16i(x, 5)),
                            assign(nth(y.loc, y.idx+6), extract_spu8x16i(x, 2)), assign(nth(y.loc, y.idx+7), extract_spu8x16i(x, 5))),
        ],
    reverse := (y,x) -> assign(vref(y,0,8), vuperm_8x16i_spu(vref(x,0,4), [8, 7, 6, 5, 4, 3, 2, 1]))
));

SIMD_ISA_DB.addISA(spu_4x32f);
SIMD_ISA_DB.addISA(spu_2x64f);
SIMD_ISA_DB.addISA(ppu_4x32f);