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

##################################################################################
#   vector strength reduction - experimental!!!

zero_one   := @.cond(e -> (IsValue(e) and (e.v=[0,1]  or e.v=[0.0,1.0])));
one_zero    := @.cond(e -> (IsValue(e) and (e.v=[1,0]  or e.v=[1.0,0.0])));

zero_mone   := @.cond(e -> (IsValue(e) and (e.v=[0,-1]  or e.v=[0.0,-1.0])));
mone_zero    := @.cond(e -> (IsValue(e) and (e.v=[-1,0]  or e.v=[-1.0,0.0])));

#   WARNING: these rules may be SSE2-specific. Especially the order of @2, @1 may be because of _mm_shuffle_pd
# NOTE: YSV reenable this

# Checks alignment of an expr.
# Pointers are assumed to be aligned.
# Type of the expression is assumed to be the "natural" one (i.e. float for 4-way)
# e.g.: for SSE_4x32f: X+3 is unaligned and X+4 is aligned
Declare(_isAligned);
_isAligned := function(c, v)
     if ObjId(c)=add then
         return ForAll(c.args, x->_isAligned(x, v));
     elif ObjId(c)=tcast then
         return _isAligned(c.args[2], v);
     elif ObjId(c) = var and ObjId(c.t)=TPtr then
         return not IsUnalignedPtrT(c.t);
     elif ObjId(c) = mul then
         return ForAny(c.args, x->_isAligned(x, v));

     elif ObjId(c) = Value and (IsVecT(c.t) or IsArrayT(c.t)) then # XXX YSV: is this correct?
	 return true;

     elif ObjId(c) = Value then
         return (Mod(c.v,v)=0);
     elif ObjId(c) = var then
         return false;
     else
         return false; #By default, return false
     fi;
     return true;
end;

_isISA := (t, vec_isa, opts) -> IsVecT(t) and t.size=vec_isa.t.size
                             and ((IsBound(opts.isas) and vec_isa in opts.isas) or
                                  (IsBound(opts.vector) and vec_isa = opts.vector.isa));

# pull "change sign" from add and sub
_rule_addSub_chs_chs := (chs) -> Rule( [@(3, [add, sub]), [chs, @(1)], [chs, @(2)]], e -> chs(ObjId(@(3).val)(@(1).val, @(2).val)));

RewriteRules(RulesStrengthReduce, rec(
    #
    # These rules replaces unaligned accesses by aligned accesses whenever possible
    #
    # This rules are disabled because _isAligned may incorrectly return true when pointer expression
    # splitted in compiler
    #
    #replace_unaligned_load := Rule(
    #    [ @(1, [vloadu_2x64f, vloadu_4x32f, vloadu_8x16i, vloadu_16x8i]),
    #      @(2).cond(e -> _isAligned(e, @(1).val.v)) ],
    #    e -> deref(tcast(TPtr(@(1).val.t), @(2).val))
    #),

    #replace_unaligned_store := Rule(
    #    [ @(1, [vstoreu_2x64f, vstoreu_4x32f, vstoreu_8x16i, vstoreu_16x8i]),
    #      @(2),
    #      @(3).cond(e -> _isAligned(@(2).val, e.t.size)) ],
    #    e -> assign(deref(tcast(TPtr(@(3).val.t), @(2).val)),@(3).val)
    #),

#     add_mul1001a := Rule([add, [mul, @(1), one_zero], [mul, @(2), zero_one]],  e ->
#         let(ins := _SIMDArch().rules.x_I_vby2[1][2], ins.instr(@(1).val, @(2).val, ins.p))), #oddly I had to switch order of @1, @2 and not [1][2]->[2][1] ??
#     add_mul1001b := Rule([add, [mul, one_zero, @(1)], [mul, zero_one, @(2)]],  e ->
#         let(ins := _SIMDArch().rules.x_I_vby2[1][2], ins.instr(@(1).val, @(2).val, ins.p))),

#     add_mul0110a := Rule([add, [mul, @(1), zero_one], [mul, @(2), one_zero]],  e ->
#         let(ins := _SIMDArch().rules.x_I_vby2[1][2], ins.instr(@(2).val, @(1).val, ins.p))),
#     add_mul0110b := Rule([add, [mul, zero_one, @(1)], [mul, one_zero, @(2)]],  e ->
#         let(ins := _SIMDArch().rules.x_I_vby2[1][2], ins.instr(@(2).val, @(1).val, ins.p))),

#     sub_mul1001b := Rule([sub, [mul, one_zero, @(1)], [mul, zero_one, @(2)]],  e ->
#         let(ins := _SIMDArch().rules.x_I_vby2[1][2], ins.instr(@(1).val, neg(@(2).val), ins.p))),
#     sub_mul0110b := Rule([sub, [mul, zero_one, @(1)], [mul, one_zero, @(2)]],  e ->
#         let(ins := _SIMDArch().rules.x_I_vby2[1][2], ins.instr(@(1).val, @(2).val, ins.p))),

# replace unaligned->aligned if possible
#    align_load:= Rule([@(1, [vloadu_2x64f, vloadu_4x32f, vloadu_8x16i]), @(2, vref, e->IsValue(e.idx) and IsInt(e.idx.ev()/@1.val.v)) ], e->vref(@2.val.loc, @2.val.idx, @1.val.v)),

    ushuffle_neg := Rule([@(1, [vushuffle_2x64f, vushuffle_4x32f]), [neg, @(2)], @(3)],
        e -> neg(ObjId(@(1).val)(@(2).val, @(3).val))),

    addsub00 := Rule([@(1,addsub_2x64f), _0, _0], e->v0),
    addsubA0 := Rule([@(1,addsub_2x64f), @(2).cond(e->ObjId(e) <> Value), _0], e->@(2).val),

    mul1A := Rule([@(1, addsub_2x64f), [@(2, mul), zero_one, @(3)], [@(4, mul), one_zero, @(5)]], e->
        addsub_2x64f(0, vshuffle_2x64f(@(5).val, @(3).val, [1,2]))),

    mul1B := Rule([@(1, addsub_2x64f), [@(2, mul), one_zero, @(3)], [@(4, mul), zero_one, @(5)]], e->
        vshuffle_2x64f(@(3).val, @(5).val, [2,1])),

    mul2A := Rule([@(1, addsub_2x64f), [@(2, mul), zero_mone, @(3)], [@(4, mul), mone_zero, @(5)]], e->
        let(v01 := Value(TVectDouble(2), [0, 1]), v10 := Value(TVectDouble(2), [1, 0]),
            vushuffle_2x64f(addsub_2x64f(v01 * @(5).val, v10 * @(3).val), [2,1]))),

    mul2B := Rule([@(1, addsub_2x64f), [@(2, mul), mone_zero, @(3)], [@(4, mul), zero_mone, @(5)]], e->
        let(v01 := Value(TVectDouble(2), [0, 1]), v10 := Value(TVectDouble(2), [1, 0]),
            neg(vshuffle_2x64f(@(3).val, @(5).val, [2,1])))),

    shuf1 := Rule([@(1, vshuffle_2x64f), @(2, vushuffle_2x64f), @(3, vushuffle_2x64f), @(4, vparam)], e->
        let(p :=@(4).val.p, p1:=@(2).val.args[2].p, p2:=@(3).val.args[2].p,
            v1 := @(2).val.args[1], v2 := @(2).val.args[1],
            vshuffle_2x64f(v1, v2, [p1[p[1]], p2[p[2]]]))),

    shuf2 := Rule([@(1, vshuffle_2x64f), @(2), @(3).cond(e->e=@(2).val), @(4, vparam)], e->
        vushuffle_2x64f(@(2).val, @(4).val.p)),

    shuf3 := Rule([@(1, vushuffle_2x64f), @(2), @(3, vparam, e->e.p = [1,2])], e-> @(2).val),

    _mm_min_4way_noSSE41_replace := Rule(@@(1,min, (e,cx) -> not(LocalConfig.cpuinfo.SIMD().hasSSE4_1()) and
        IsBound(cx.opts.vector) and
            cx.opts.vector.isa=SSE_4x32i and
            e.t=TVect(TReal, 4)),
	e -> bin_or(bin_andnot(gt(@@(1).val.args[2],@@(1).val.args[1]),@@(1).val.args[2]),
	            bin_and(gt(@@(1).val.args[2],@@(1).val.args[1]),@@(1).val.args[1]))),

    _rshift_16way_replace := Rule(@@(1, bin_shr, (e,cx) -> (
            IsBound(cx.opts.vector) and
            cx.opts.vector.isa=SSE_16x8i and
            e.t=cx.opts.vector.isa.t)),
	e -> bin_and(tcast(e.t, bin_shr(tcast(TVect(TReal, 8), @@(1).val.args[1]), @@(1).val.args[2])),
	             vdup(TInt.value(bin_shr(256,@@(1).val.args[2])-1), 16))),

    #note: this does a gte and not a gt
    _gt_16way_unsigned_replace := Rule(@@(1, gt, (e,cx) -> (
            IsBound(cx.opts.vector) and
            cx.opts.vector.isa=SSE_16x8i and
            not (cx.opts.vector.isa.isSigned) and
            e.t=cx.opts.vector.isa.t)),
        e -> eq(min(@@(1).val.args[2], @@(1).val.args[1]), @@(1).val.args[2])
    ),

    vload1_4x32f_cond := Rule( [vload1_4x32f, @(1, cond)],
        e -> _map_cond(@(1).val, p->p, exp->vload1_4x32f(exp))),
    vload1_4x32f_funcExp := Rule( [vload1_4x32f, [funcExp, @(1, Value)]],
        e -> e.t.value([@(1).val, 0, 0, 0])),  # must be _mm_set_ss

    vload_2h_4x32f_cond := Rule( [vload_2h_4x32f, @(1), @(2, cond)],
        e -> _map_cond(@(2).val, p->p, exp->vload_2h_4x32f(@(1).val, exp))),
    vload_2l_4x32f_cond := Rule( [vload_2l_4x32f, @(1), @(2, cond)],
        e -> _map_cond(@(2).val, p->p, exp->vload_2l_4x32f(@(1).val, exp))),

    vload_2h_4x32f_funcExp := Rule( [vload_2h_4x32f, @(1, Value), [funcExp, @(2, Value)]],
        e -> vshuffle_4x32f(@(1).val, @(2).val, [1,2,3,4])),
    vload_2l_4x32f_funcExp := Rule( [vload_2l_4x32f, @(1, Value), [funcExp, @(2, Value)]],
        e -> vshuffle_4x32f(@(2).val, @(1).val, [1,2,3,4])),

    vshuffle_4x32f_cond_r := Rule( [vshuffle_4x32f, @(1), @(2, cond), @(3)],
        e -> _map_cond(@(2).val, p->p, exp->vshuffle_4x32f(@(1).val, exp, @(3).val))),
    vshuffle_4x32f_cond_l := Rule( [vshuffle_4x32f, @(1, cond), @(2), @(3)],
        e -> _map_cond(@(1).val, p->p, exp->vshuffle_4x32f(exp, @(2).val, @(3).val))),
    vushuffle_4x32f_cond := Rule( [vushuffle_4x32f, @(1, cond), @(2)],
        e -> _map_cond(@(1).val, p->p, exp->vshuffle_4x32f(exp, @(2).val))),

));

Class(RulesPreStrengthReduce, RuleSet);
RewriteRules(RulesPreStrengthReduce, rec(
    _avg_16way_replace := Rule([@@(1, add, (e,cx) -> (
            IsBound(cx.opts.vector) and
            cx.opts.vector.isa=SSE_16x8i and
            e.t=cx.opts.vector.isa.t)),[bin_shr,@(2),@(3,Value,e->e.v=1)],[bin_shr,@(4),@(5,Value,e->e.v=1)]],
        (e, cx)-> cx.opts.vector.isa.average(@(2).val,@(4).val)),
));

# recursive condition function for push_change_sign rule.
# if tree consists from sub and add with [mul, Value, @] leaves only  we can
# merge "change sign" with values.

Declare(_canEliminateChangeSign);
_canEliminateChangeSign := (tree) -> CondPat( tree,
    add, ForAll(tree.args, _canEliminateChangeSign),
    sub, ForAll(tree.args, _canEliminateChangeSign),
    [mul, Value, @], true,
    false);

RewriteRules(RulesStrengthReduce, rec(
    require_load := Rule(@@(1, deref, (e,cx) -> IsBound(cx.opts.vector) and
	    IsBound(cx.opts.vector.isa.requireLoad) and cx.opts.vector.isa.requireLoad and
        ((IsBound(cx.assign) and cx.assign<>[] and cx.assign[1].exp = @@(1).val) or (ForAny([add, sub, mul,
            vunpacklo_4x32f, vunpackhi_4x32f, addsub_4x32f, hadd_4x32f, vshuffle_4x32f, vushuffle_4x32f], i-> IsBound(cx.(i.name)) and cx.(i.name)<>[] ))) ),
#        ((IsBound(cx.assign) and cx.assign<>[] and ObjId(cx.assign[1].exp) = deref and ObjId(cx.assign[1].loc) <> deref) or (ForAny([add, sub, mul, vunpacklo_4x32f], i-> IsBound(cx.(i.name)) and cx.(i.name)<>[] ))) ),
        (e, cx)-> cx.opts.vector.isa.loadop(@@(1).val.loc)),

    require_store := Rule([@@(1, assign, (e,cx) -> (
            IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa.requireStore) and cx.opts.vector.isa.requireStore)),
            @(2, deref), @(3)],
        (e, cx)-> cx.opts.vector.isa.storeop(@(2).val, @(3).val)),

    require_store_var_SSE_2x32f := Rule([@@(1, assign, (e,cx) -> (IsBound(cx.opts.vector) and cx.opts.vector.isa = SSE_2x32f)),
            @(2, deref), @(3, Value)],
        (e, cx)-> let(vv := var.fresh_t("V", TVect(TReal, cx.opts.vector.isa.v)),
            decl(vv, chain(assign(vv, @(3).val), cx.opts.vector.isa.storeop(@(2).val, vv))))),

    fix_unpackhi_2x32f := Rule(@(1, vunpackhi_2x32f),
        e -> vshuffle_4x32f(vunpacklo_4x32f(@(1).val.args[1], @(1).val.args[2]), vunpacklo_4x32f(@(1).val.args[1], @(1).val.args[2]), vparam([3,4,3,4]))),

    fix_unpacklo_2x32f := Rule(@(1, vunpacklo_2x32f), e -> vunpacklo_4x32f(@(1).val.args[1], @(1).val.args[2])),

    # fix_vzero_2x32f := Rule(@(1, vzero_2x32f), e -> vzero_4x32f()),

    fix_vload_2x32f := Rule(@(1, vload_2x32f), e -> vload_2l_4x32f(@(1).val.args[1], @(1).val.args[2])),
    fix_vstore_2x32f := Rule(@(1, vstore_2x32f), e -> vstore_2l_4x32f(@(1).val.loc, @(1).val.exp)),
    fix_addsub_2x32f := Rule(@(1, addsub_2x32f), e -> addsub_4x32f(@(1).val.args[1], @(1).val.args[2])),

    addsub_zero_r := Rule([@(1, addsub_4x32f), @(2), _0], e -> @(2).val),

    # NOTE: verify this rule
    addsub_zero_l := Rule([@(1, addsub_4x32f), _0, @(2)], e -> chslo_4x32f(@(2).val)),

    chslo_4x32f_neg := Rule([chslo_4x32f, [neg, @(1)]], e -> chshi_4x32f(@(1).val)),
    chshi_4x32f_neg := Rule([chshi_4x32f, [neg, @(1)]], e -> chslo_4x32f(@(1).val)),
    chslo_2x64f_neg := Rule([chslo_2x64f, [neg, @(1)]], e -> chshi_2x64f(@(1).val)),
    chshi_2x64f_neg := Rule([chshi_2x64f, [neg, @(1)]], e -> chslo_2x64f(@(1).val)),

    chslo_4x32f_neg_2 := Rule([neg, [chslo_4x32f, @(1)]], e -> chshi_4x32f(@(1).val)),
    chshi_4x32f_neg_2 := Rule([neg, [chshi_4x32f, @(1)]], e -> chslo_4x32f(@(1).val)),
    chslo_2x64f_neg_2 := Rule([neg, [chslo_2x64f, @(1)]], e -> chshi_2x64f(@(1).val)),
    chshi_2x64f_neg_2 := Rule([neg, [chshi_2x64f, @(1)]], e -> chslo_2x64f(@(1).val)),

    # try to use same "change sign" everywhere
    add_@_chshi_4x32f := Rule([add, @(1), @(2, chshi_4x32f, x -> not (ObjId(@(1).val) in [chslo_4x32f, chshi_4x32f]))],
        e -> sub(@(1).val, chslo_4x32f(@(2).val.args[1]))),

    # put value to front in mul
    # NOTE: ESReduce has something like this one; rules below work only when Value is in front.
    mul_value_to_front := Rule( [mul, ...,  @(1), @(2, Value, x -> not IsValue(@(1).val)), ...],
        e -> let( a := SplitBy(e.args, IsValue), ApplyFunc(mul, [Product(a[1])] :: a[2]))),

    # "change sign" elimination
    mul_val_chs := ARule( mul, [@(1, Value), [@(2, [chshi_2x64f, chslo_2x64f, chshi_4x32f, chslo_4x32f]), @(3)] ],
        e -> let( chs := @(2).val,
                  [@(1).val * ObjId(chs)(chs.t.one()).ev(), @(3).val] )),

    chs_mul_val := Rule( [@(2, [chshi_2x64f, chslo_2x64f, chshi_4x32f, chslo_4x32f]), [mul, @(1, Value), @(3)]],
        e -> let( chs := @(2).val,
                  mul(@(1).val * ObjId(chs)(chs.t.one()).ev(), @(3).val))),

    mul_val_vushuffle_2x64f_chs := ARule( mul, [@(1, Value), [vushuffle_2x64f, [@(2, [chshi_2x64f, chslo_2x64f]), @(3)], @(4)] ],
        e -> let( chs := @(2).val,
                  vp  := @(4).val,
                  val := vushuffle_2x64f( ObjId(chs)(chs.t.one()).ev(), vp).ev(),
                  [@(1).val * val, vushuffle_2x64f( @(3).val, vp)] )),

    mul_val_vushuffle_4x32f_chs := ARule( mul, [@(1, Value), [vushuffle_4x32f, [@(2, [chshi_4x32f, chslo_4x32f]), @(3)], @(4)] ],
        e -> let( chs := @(2).val,
                  vp  := @(4).val,
                  val := vushuffle_4x32f( ObjId(chs)(chs.t.one()).ev(), vp).ev(),
                  [@(1).val * val, vushuffle_4x32f( @(3).val, vp)] )),

    # pull "change sign" from [add, sub]
    addSub_chshi_2x64f_chshi_2x64f := _rule_addSub_chs_chs(chshi_2x64f),
    addSub_chslo_2x64f_chslo_2x64f := _rule_addSub_chs_chs(chslo_2x64f),
    addSub_chshi_4x32f_chshi_4x32f := _rule_addSub_chs_chs(chshi_4x32f),
    addSub_chslo_4x32f_chslo_4x32f := _rule_addSub_chs_chs(chslo_4x32f),

    # push change sign into mul so it can be eliminated later
    push_change_sign := Rule([@(1, [chshi_2x64f, chslo_2x64f, chshi_4x32f, chslo_4x32f]), @(2, [add, sub], _canEliminateChangeSign)],
       e -> SubstTopDownNR( @(2).val, [mul, @(3, Value), @(4)], x -> mul(@(3).val, ObjId(@(1).val)(@(4).val)))),

    # pull same unary shuffle from add, sub and mul
    addSubMul_vushuffle_2x64f_vushuffle_2x64f := Rule( [@(5, [add, sub, mul]), [vushuffle_2x64f, @(1), @(2)], [vushuffle_2x64f, @(3), @(4).cond(x -> x=@(2).val)]],
        e -> vushuffle_2x64f( ObjId(@(5).val)( @(1).val, @(3).val ), @(2).val )),
    addSubMul_vushuffle_4x32f_vushuffle_4x32f := Rule( [@(5, [add, sub, mul]), [vushuffle_4x32f, @(1), @(2)], [vushuffle_4x32f, @(3), @(4).cond(x -> x=@(2).val)]],
        e -> vushuffle_4x32f( ObjId(@(5).val)( @(1).val, @(3).val ), @(2).val )),

    # pull value multiplier from unary shuffle. Disabled for now as it causing stalls on Core i7 2x64f
    # vushuffle_mul_value := Rule( [@(1, [vushuffle_2x64f, vushuffle_4x32f]), [mul, @(2, Value), @(3)], @(4)],
    #    e -> mul( ObjId(@(1).val)(@(2).val, @(4).val), ObjId(@(1).val)(@(3).val, @(4).val)) ),

    # V1*A + V2*vushuffle(A, P), V1*V2=0 => (V1+V2)*vushuffle(A, P`)
    merge_add_mul_mul_vushuffle_4x32f := ARule( add, [ [mul, @(1, Value), @(2)], [mul, @(3, Value), [@(0, vushuffle_4x32f), @(4), @(5).cond(x -> @(4).val = @(2).val and @(3).val*@(1).val = @(0).val.t.zero())]] ],
        e -> let( vp := @(5).val.p,
                  V1 := @(1).val,
                  V2 := @(3).val,
                  new_vp := List([1..Length(vp)], i -> When( V1.v[i] = 0, vp[i], i)),
                  [ mul( add(V1 + V2), vushuffle_4x32f( @(4).val, new_vp ))] )),

    # V1*A + vushuffle(V2*A, P), V1*vushuffle(V2,P)=0 => (V1+vushuffle(V2,P))*vushuffle(A, P`)
    merge_add_mul_vushuffle4x32f_mul := ARule( add, [ [mul, @(1, Value), @(2)], [@(0, vushuffle_4x32f), [mul, @(3, Value), @(4)], @(5).cond(x -> @(4).val = @(2).val and vushuffle_4x32f(@(3).val, x).ev()*@(1).val = @(0).val.t.zero())] ],
        e -> let( vp := @(5).val.p,
                  V1 := @(1).val,
                  V2 := @(3).val,
                  new_vp := List([1..Length(vp)], i -> When( V1.v[i] = 0, vp[i], i)),
                  [ mul( add(V1 + vushuffle_4x32f(V2, vp).ev()), vushuffle_4x32f( @(4).val, new_vp ))] )),

    remove_mul_load_2l_4x32f := Rule( [mul, @(1, Value), [@(2, vload_2l_4x32f, x -> @(1).val * x.t.value([ 1, 1, 0, 0]) = @(1).val), _0, @]],
        e -> @(2).val),

    store_zero := Rule([@(1, vstore_2l_4x32f), @(2), _0], e -> vstore_2l_4x32f(@(2).val, vzero_4x32f())),

    fix_vushuffle_2x32f := Rule([@(1, vushuffle_2x32f), @(2).cond(e->ObjId(e) <> Value), @(3)],
        e -> vshuffle_4x32f(@(1).val.args[1], @(1).val.args[1], vparam(Concat(@(1).val.args[2].p, @(1).val.args[2].p)))),

#   FF: NOTE: Why are all thes unaligned stores showing up?? This is a temporary fix. Yevgen, please tell me!
    fix_vstoreu_2x32f := Rule(@(1, vstoreu_2x32f), e -> vstore_2l_4x32f(@(1).val.loc, @(1).val.exp)),

    remove_shuffle := Rule([@(1, vstore_2l_4x32f), @(2), @(3, vushuffle_4x32f, e->e.args[2].p=[3,4,3,4])],
        e -> vstore_2h_4x32f(@(2).val, @(3).val.args[1])),

#    propagate_vushuffle := Rule(@(1, [add, sub, mul], e->ForAll(e.args, i->ObjId(i)=vushuffle_4x32f)),
#        e -> Error("Chaught")),
#
    assign_zero_xor := Rule([@@(1, assign, (e,cx)->IsBound(cx.opts.vector) and cx.opts.vector.isa = SSE_2x32f), @(2), @(3, Value, e->e.v=0)],
        (e, cx) -> let(zv := var.fresh_t("Z", TVect(TReal, cx.opts.vector.isa.v)), decl(zv, assign(@(2).val, vzero_2x32f())))),

    sreduce_vushuffle_1 := Rule([@(1, vushuffle_2x32f), @(2, Value, e->ObjId(e.t)=TVect), @(3)],
        e -> Value(@(2).val.t, List([1..@(2).val.t.size], i->@(2).val.v[@(3).val.p[i]]))),

    sreduce_vushuffle_2 := Rule([@(1, vushuffle_2x32f), @(2, Value, e->e.t=TInt), @(3)],
        e -> @(2).val),

#    fix_noneExp := Rule(@(1, noneExp), e->e.t.value(0)),

    addsub_noneExp_l := Rule([@(1, [addsub_2x64f, addsub_4x32f]), @(2, noneExp), @(3)], (e,cx)-> ObjId(@(1).val)(cx.opts.vector.isa.vzero(), @(3).val)),
    addsub_noneExp_r := Rule([@(1, [addsub_2x64f, addsub_4x32f]), @(2), @(3, noneExp)], (e,cx)-> @(2).val),

    add_addsub_zero_l := Rule([add, @(3), [@(1, [addsub_2x64f, addsub_4x32f]), _0, @(2)]], e -> ObjId(@(1).val)(@(3).val, @(2).val)),
    add_addsub_zero_r := Rule([add, [@(1, [addsub_2x64f, addsub_4x32f]), _0, @(2)], @(3)], e -> ObjId(@(1).val)(@(3).val, @(2).val)),

    addsub_addsub_zero_l := Rule( [@(1, [addsub_2x64f, addsub_4x32f]), [@(2, [addsub_2x64f, addsub_4x32f]), _0, @(3)], @(4)],
        e -> ObjId(@(1).val)(@(0).val, add(@(3).val, @(4).val))),
    addsub_addsub_zero_r := Rule( [@(1, [addsub_2x64f, addsub_4x32f]), @(4), [@(2, [addsub_2x64f, addsub_4x32f]), _0, @(3)]],
        e -> add(@(3).val, @(4).val)),

    addsub_zero_neg  := Rule([@(1, [addsub_2x64f, addsub_4x32f]), _0, [neg, @(2)]], e -> neg(ObjId(@(1).val)(@(0).val, @(2).val))),
    sub_addsub_zero := Rule([sub, [@(1, [addsub_2x64f, addsub_4x32f]), _0, @(2)], [@(3, [addsub_2x64f, addsub_4x32f]), _0, @(4)]],
        e -> ObjId(@(1).val)(@(0).val, sub(@(2).val, @(4).val))),

    sse_vshuffle_noneExp2 := Rule([@(1, [vshuffle_2x64f, vshuffle_4x32f]), @(2, noneExp), @(3, noneExp), @(4)], e->@(2).val),

    sse_vshuffle_noneExp1L := Rule([@(1, [vshuffle_2x64f, vshuffle_4x32f]), @(2, noneExp), @(3), @(4)],
	(e,cx)-> ObjId(@(1).val)(cx.opts.vector.isa.vzero(), @(3).val, @(4).val)),

    sse_vshuffle_noneExp1R := Rule([@(1, [vshuffle_2x64f, vshuffle_4x32f]), @(2), @(3, noneExp), @(4)],
	(e,cx)-> ObjId(@(1).val)(@(2).val, cx.opts.vector.isa.vzero(), @(4).val)),

    sse_unary_noneExp := Rule(
	[@(1, [
          vushuffle_16x8i, vushuffle_2x32f, vushuffle_2x64f, vushuffle_2x64i,
	  vushuffle_4x32f, vushuffle_4x32i,
	  chslo_2x32f, chslo_2x64f, chslo_2x64i, chslo_4x32f,
	  chshi_2x32f, chshi_2x64f, chshi_2x64i, chshi_4x32f ]),
	 @(2, noneExp), ...],
	e -> @(2).val),

## SSE_8x16i SSSE3 rules
    vushuffle_8x16i := Rule([@(1, vushuffle_8x16i), @(2, Value, e->ObjId(e.t)=TVect), @(3)],
        e -> Value(@(2).val.t, List(2*[0..@(2).val.t.size-1], i->@(2).val.v[@(3).val.v[i+1].v/2+1]))),

# complex loopcode load operations rules
    SSE_4x32f_load_TComplex := Rule([@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and
        cx.opts.vector.isa = SSE_4x32f),
        [@(2,re), nth], @(3, im, e->e.args[1]=@(2).val.args[1]), @(4,re, e->e.args[1]=@(2).val.args[1]), @(5,im, e->e.args[1]=@(2).val.args[1])],
        e->let(dptr := @(2).val.args[1].toPtr(TVect(TReal, 2)),
            vushuffle_4x32f(vload_2l_4x32f(vzero_4x32f(),dptr), [1,2,1,2]))),

    SSE_4x32f_fold_vushuffle := Rule([@(1, vushuffle_4x32f), @(2, vushuffle_4x32f), @(3)],
        e->vushuffle_4x32f(@(2).val.args[1], List(@(1).val.args[2].p, i->@(2).val.args[2].p[i]))),

    SSE_4x32f_fold_vushuffle_vshuffle := Rule([@(1, vushuffle_4x32f), [vshuffle_4x32f, @(2), @(3), @(4)], @(5)],
        e->vshuffle_4x32f(@(2).val, @(3).val, List(@(5).val.p, i->@(4).val.p[i]))),

    SSE_4x32f_load_TVect := Rule([@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and
        cx.opts.vector.isa = SSE_4x32f),
        @(2,nth,e->IsBound(e.loc.init)), @(3, nth, e->e.loc=@(2).val.loc), @(4,nth, e->e.loc=@(2).val.loc), @(5,nth, e->e.loc=@(2).val.loc)],
        e->deref(@(2).val.toPtr(TVect(TReal, 4)))),

    # movldup is an SSE3 instruction
    SSE_4x32f_movldup := Rule([@@(1, vushuffle_4x32f,
        (e,cx) -> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.SIMD) and not cx.opts.vector.SIMD in ["SSE2", "SSE", "MMX"]),
        @(2).cond(e->ObjId(e) <> Value), @(3, vparam, e->e.p=[1,1,3,3])],
        e->vldup_4x32f(@(2).val)),

    # movhdup is an SSE3 instruction
    SSE_4x32f_movhdup := Rule([@@(1,vushuffle_4x32f,
        (e,cx) -> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.SIMD) and not cx.opts.vector.SIMD in ["SSE2", "SSE", "MMX"]),
        @(2).cond(e->ObjId(e) <> Value), @(3, vparam, e->e.p=[2,2,4,4])],
        e->vhdup_4x32f(@(2).val)),

    SSE_2x64f_load_TVect := Rule([@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and
        cx.opts.vector.isa = SSE_2x64f),
        @(2,nth,e->IsBound(e.loc.init)), @(3, nth, e->e.loc=@(2).val.loc)],
        e->deref(@(2).val.toPtr(TVect(TReal, 2)))),

    SSE_16x8i_add_ddiv2 := ARule(add, [[ddiv, @(1), @(2, Value, x -> x=2)], ..., [@(3, ddiv), @(4),
        @@(5, Value, (x, cx) -> x=2 and _isISA(@(3).val.t, SSE_16x8i, cx.opts) and IsUIntT(@(3).val.t.base_t()))]],
        e -> [average_16x8i(@(1).val, @(4).val)]),
    SSE_16x8i_add_ddiv2_absdiff2 := ARule(add, [[ddiv, @(1, absdiff2), @(2, Value, x -> x=2)], ..., [@(3, ddiv), @(4, absdiff2),
        @@(5, Value, (x, cx) -> x=2 and _isISA(@(3).val.t, SSE_16x8i, cx.opts))]],
        e -> [average_16x8i(@(1).val, @(4).val)]),

    SSE_16x8i_didiv2_add := Rule([@(0, [ddiv, idiv]), [add, @(1), @(2)],
        @@(3, Value, (x, cx) -> x=2 and _isISA(@(0).val.t, SSE_16x8i, cx.opts) and IsUIntT(@(0).val.t.base_t()))],
        e -> [average_16x8i(@(1).val, @(2).val)]),
    SSE_16x8i_didiv2_add_absdiff2 := Rule([@(0, [ddiv, idiv]), [add, @(1, absdiff2), @(2, absdiff2)],
        @@(3, Value, (x, cx) -> x=2 and _isISA(@(0).val.t, SSE_16x8i, cx.opts))],
        e -> [average_16x8i(@(1).val, @(2).val)]),
));

Class(RulesSSEPostProcess, RuleSet);
RewriteRules(RulesSSEPostProcess, rec(
    div_16x := Rule( [ @@(1, [idiv, ddiv], (x, cx) -> IsOrdT(x.t.base_t()) and _isISA(x.t, SSE_16x8i, cx.opts)),
                       @(2), @(3, Value, x -> let( v := Set(Flat([x.v])), Length(v)=1 and 2^CeilLog2(_unwrap(v[1])) = v[1])) ],
        e -> let( v :=  _unwrap(Set(Flat([@(3).val.v]))[1]),
                  bin_and(tcast(e.t, bin_shr(tcast(TVect(T_Int(16), 8), @(2).val), CeilLog2(v))), e.t.value(idiv(255, v))))),

    SSE_absdiff2_to_xor := Rule( @(1, absdiff2, x -> IsOrdT(x.t.base_t())),
        e -> bin_xor(e.args[1], e.args[2])),

    SSE_fixUnalignedLoadStore := Rule(@@(1,[vstoreu2_4x32f, vstoreu_4x32f],(e,cx)->ObjId(e.args[2])=noneExp and cx.opts.fixUnalignedLoadStore = true), e->skip())
));


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

Class(VectorCodegen, DefaultCodegen, rec(

    Formula := meth(self, o, y, x, opts)
        local icode, datas, prog, params, sub, initsub, io, initcode;

        o := SumsUnification(o.child(1), opts);

        [x, y] := self.initXY(x, y, opts);

        o :=  Process_fPrecompute(o, opts);

        params := Set(Filtered(Collect(o, @(1, [param, var])), x -> ObjId(x)=param or IsParallelLoopIndex(x)));

        datas := Filtered(Collect(o, FDataOfs), x->IsBound(x.var.init));
        o := BlockSumsOpts(o, opts);
        icode := self(o, y, x, opts);
        if IsBound(opts.finalSReduce) and opts.finalSReduce then
            icode := SReduce(icode, opts);   #SReduce -> ESReduce sequence is faster than just ESReduce
            icode := ESReduce(icode, opts);
        fi;
        icode := RemoveAssignAcc(icode);
        icode := BlockUnroll(icode, opts);
        icode := DeclareHidden(icode);
        if IsBound(opts.isFixedPoint) and opts.isFixedPoint then
            icode := FixedPointCode(icode, opts.bits, opts.fracbits);
        fi;

        io := When(x=y, [x], [y, x]);
        sub := Cond(IsBound(opts.subName), opts.subName, "transform");
        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "init");
        icode := func(TVoid, sub, Concatenation(io, params), icode);

        if IsBound(opts.generateInitFunc) and opts.generateInitFunc then
	    initcode := chain(List(datas, x -> SReduce(x.var.init, opts)));
            prog := program(
                decl(List(datas, x->x.var),
                    chain(
                        func(TVoid, initsub, params :: Set(Collect(initcode,param)), initcode),
                        icode
                    )));
        else
            prog := program( func(TVoid, initsub, params, chain()), icode);
        fi;

        # FF: I really don't know why suddenly AVX_8x32f requires me to do that !!
        #if IsBound(opts.vector.isas) then
        #    prog := FoldR(opts.isas, (r, e) -> e.fixProblems(r, opts), prog);
        #fi;
        #if IsBound(opts.vector.isa.fixProblems) then prog := opts.vector.isa.fixProblems(prog, opts); fi;

        prog.dimensions := o.dims();
        if IsBound(opts.finalSReduce) and opts.finalSReduce then
            icode := SReduce(icode, opts);   #SReduce -> ESReduce sequence is faster than just ESReduce
            icode := ESReduce(icode, opts);
        fi;
        return prog;
    end,

###########################################################################

    NoPull := (self, o, y, x, opts) >> self(o.child(1), y, x, opts),

    VContainer := (self, o, y, x, opts) >>
        self(o.child(1), y, x, CopyFields(opts, rec(
            vector := rec(
                isa  := o.isa,
                SIMD := LocalConfig.cpuinfo.SIMDname )))),

    # VirtualPad changes dimensions of the SPL object but does not
    # affect the code
    VirtualPad := (self, o, y, x, opts) >> self(o.child(1), y, x, opts),

    VPrm_x_I := (self, o, y, x, opts) >> self(VTensor(Prm(o.func), o.v), y, x, opts),

    VPerm := (self, o, y, x, opts) >> o.code(y, x), # these codes are generated

    #VTensor for multi inputs
    VTensor := (self, o, y, x, opts) >> let(
        CastToVect := p -> StripList(List(Flat([p]), e -> tcast(TPtr(TVect(opts.vector.isa.t.t, o.vlen)), e))),
        self(o.child(1), CastToVect(y), CastToVect(x), opts)),

    # ---------------
    # Gather
    # ---------------
    VGath := (self, o, y, x, opts) >> Cond(IsUnalignedPtrT(x.t),
        self(VGath_u(fTensor(o.func, fBase(o.v, 0)), o.v), y, x, opts),
        self(VTensor(Gath(o.func), o.v), y, x, opts)),

    VGath_u := (self, o, y, x, opts) >> let(
        j    := Ind(o.func.domain()),
        xofs := o.func.at(j),
        loop(j, j.range,
            opts.vector.isa.loadCont(o.v, y, j, x, xofs, imod(xofs, o.v), opts))),

    VGath_dup := (self, o, y, x, opts) >> let(
        v := o.v, func := o.func.lambda(), j := Ind(o.func.domain()),
        isa := opts.vector.isa,
        loop(j, j.range,
             isa.dupload(vtref(isa.t, y, j), nth(x, func.at(j))))),

    VReplicate := (self, o, y, x, opts) >> let(
	v := o.v, isa := opts.vector.isa,
        chain(List([0..v-1], i -> isa.duploadn(vtref(isa.t, y, i), vtref(isa.t, x, 0), i+1)))),

    _VHAdd := (self,xlist,opts) >> let(
       l := Length(xlist),
       Cond(l=2, opts.vector.isa.hadd(xlist[1], xlist[2]),
	    # else
	         opts.vector.isa.hadd(self._VHAdd(xlist{[1..l/2]}, opts),
		                      self._VHAdd(xlist{[l/2+1..l]}, opts)))),

    VHAdd := (self, o, y, x, opts) >> let(v := o.v, isa := opts.vector.isa,
        assign(vtref(isa.t, y, 0), self._VHAdd(List([0..v-1], i -> vtref(isa.t, x, i)), opts))),

    VGath_zero := (self, o, y, x, opts) >> self(VGath(fAdd(o.N, o.n, 0), o.v), y, x, opts),

    VScat_sv := meth(self, o, y, x, opts)
        local v, sv, func, nv1, rem, ii, id, svstore, isa;
        [v, sv, func] := [o.v, o.sv, o.func.lambda()];
        isa := opts.vector.isa;
        nv1 := idiv(func.domain()*sv, v);
        rem := o.getConstantRem();
        ii  := Ind(nv1);
        id  := 1+Log2Int(sv);
        svstore  := (n, id2, idx) -> isa.svstore[id][id2](
            List([0..n-1], i -> nth(y, sv*func.at(i + (v/sv)*idx))),
            vtref(isa.t, x, idx), opts);
        return chain(
            loop(ii, ii.range, svstore(v/sv, v/sv, ii)),
            When(rem=0, [],    svstore(rem, rem, nv1)));
    end,

    VScat_svAcc := meth(self, o, y, x, opts)
        local v, sv, func, nv1, rem, ii, id, svaccstore, tmp, tmp2, isa;
        [v, sv, func] := [o.v, o.sv, o.func.lambda()];
        nv1 := idiv(func.domain()*sv, v);
        #ESReduce should do the equivalent of an EvalScalar if possible
        #and maybe come up with smarter reductions if not possible
        # rem := ESReduce(imod(func.domain()*sv, v) / sv, opts);  <-- keep this??
        rem := o.getConstantRem();
        isa := opts.vector.isa;
        ii  := Ind(nv1);
        id  := 1+Log2Int(sv);
        tmp := TempVar(isa.t);
        tmp2:= TempVar(isa.t);
        svaccstore  := (n, id2, idx) -> chain(
            isa.svload[id][id2](
                tmp,
                List([0..n-1], i -> nth(y, sv*func.at(i + (v/sv)*idx))), opts),
            assign(tmp2, tmp + vtref(isa.t, x, idx)),
            isa.svstore[id][id2](
                List([0..n-1], i -> nth(y, sv*func.at(i + (v/sv)*idx))),
                tmp2, opts));
        return chain(
            loop(ii, ii.range, svaccstore(v/sv, v/sv, ii)),
            When(rem=0, [],    svaccstore(rem, rem, nv1)));
    end,

    VGath_sv := meth(self, o, y, x, opts)
        local v, sv, func, nv1, rem, ii, id, svload, isa;
        [v, sv, func] := [o.v, o.sv, o.func.lambda()];
        nv1 := idiv(func.domain()*sv, v);
        rem := o.getConstantRem();
        isa := opts.vector.isa;
        ii := Ind(nv1);
        id := 1+Log2Int(o.sv);
        svload  := (n, id2, idx) -> isa.svload[id][id2](
            vtref(isa.t, y, idx),
            List([0..n-1], i -> nth(x, sv*func.at(i + (v/sv)*idx))), opts);
        return chain(
            loop(ii, ii.range, svload(v/sv, v/sv, ii)),
            When(rem=0, [],    svload(rem, rem, nv1)));
    end,

    RCVGath_sv := meth(self, o, y, x, opts)
        local v, sv, func, nv1, res, ii, id, svload, n, isa;
        [v, sv, func] := [o.v, o.sv, fTensor(o.func, fId(2*o.sv)).lambda()];
        n := o.func.domain()*sv;
        nv1 := idiv(n, v);
        res := EvalScalar((n - v*nv1)/sv);
        ii  := Ind(2*nv1);
        id := Log2Int(2*sv)+1;
        isa := opts.vector.isa;
        svload := (n,id2, idx) -> isa.svload[id][id2](
            vtref(isa.t, y, idx),
            List([0..n-1], i -> nth(x, func.at(2*i*sv + v*idx))), opts);
        # FF: NOTE: This strength reduction call should not be necessary. However, looks like at this point SAR requires it
        return RulesMergedStrengthReduce(chain(
            loop(ii, ii.range, svload(v/sv, v/id, ii)),
            Cond(res=0,         [  ],
                 sv*res <= v/2, [ svload(res, res, 2*nv1),
                          assign(vtref(isa.t, y, (2*nv1+1)), isa.t.zero()) ],
                        [ svload(v/sv, v/id, 2*nv1),
                  svload(res-v/(2*sv), res-v/id, 2*nv1+1) ])));
    end,

    RCVScat_sv := meth(self, o, y, x, opts)
        local v, sv, func, nv1, res, ii, id, svstore, n, isa;
        [v, sv, func] := [o.v, o.sv, fTensor(o.func, fId(2*o.sv)).lambda()];
        n := o.func.domain()*sv;
        nv1 := idiv(n, v);
        res := EvalScalar((n - v*nv1)/sv);
        ii  := Ind(2*nv1);
        id := Log2Int(2*sv)+1;
        isa := opts.vector.isa;
        svstore := (n,id2, idx) -> isa.svstore[id][id2](
        List([0..n-1], i -> nth(y, func.at(2*i*sv + v*idx))),
        vtref(isa.t, x, idx), opts);
        return chain(
            loop(ii, ii.range, svstore(v/sv, v/id, ii)),
            Cond(res=0,         [  ],
                 sv*res <= v/2, [ svstore(res, res, 2*nv1) ],
                        [ svstore(v/sv, v/id, 2*nv1),
                  svstore(res-v/(2*sv), res-v/id, 2*nv1+1) ]));
    end,

   VStretchGath := (self, o, y, x, opts) >> let(
       v := o.v,
       func := o.func.lambda(),
       rat := func.domain() / o.part,
       nv1 := idiv(rat, v),
       res := EvalScalar(rat - v*nv1),
       ii := Ind(),
       it := Ind(),
       xbase := rat * it,
       ybase := (_roundup(rat, v) / v) * it,
       isa := opts.vector.isa,
       svload := isa.svload[1][v],

       footer := When(res=0, skip(),
           let(svloadR := isa.svload[1][res],
               svloadR(vtref(isa.t, y, nv1 + ybase), List([0..res-1],
                       i->nth(x, func.at(nv1*v + i + xbase))), opts))),
       loop(it, o.part,
           chain(
               loop(ii, nv1,
                   svload(vtref(isa.t, y, ii + ybase),
                       List([0..v-1], i->nth(x, func.at(v * ii + i + xbase))), opts)),
               footer))
    ),

   vRCStretchGath := (self, o, y, x, opts) >> let(
       v := o.v/2,
       func := o.func.lambda(),
       rat := func.domain() / o.part,
       nv1 := idiv(rat, v),
       res := EvalScalar(rat - v*nv1),
       ii := Ind(),
       it := Ind(),
       xbase := rat * it,
       ybase := (_roundup(rat, v) / v) * it,
       isa := opts.vector.isa,
       svload := isa.svload[2][v],
       footer := When(res=0, skip(),
           let(svloadR := isa.svload[2][res],
               svloadR(vtref(isa.t, y, nv1 + ybase), List([0..res-1], i -> nth(x, 2*func.at(nv1*v + i + xbase))), opts))),
       loop(it, o.part,
           chain(
               loop(ii, nv1,
                   svload(vtref(isa.t, y, ii + ybase), List([0..v-1], i -> nth(x, 2*func.at(v*ii + i + xbase))), opts)),
               footer))
    ),

   RCVStretchGath := (self, o, y, x, opts) >> let(
       v    := o.v,
       func := o.func.lambda(),
       rat  := o.func.domain()/o.part,
       nv1  := idiv(rat, v),
       res  := EvalScalar(rat-v*nv1),
       ii   := Ind(),
       it   := Ind(),
       isa  := opts.vector.isa,
       svload := isa.svload[2][v/2],
       ybase := (2 * _roundup(rat, v) / v) * it,
       xbase := rat * it,

       footer := Cond(
           res=0,
              skip(),

           res<=v/2, let(svloadR := isa.svload[2][res],
               chain(
                   svloadR(vtref(isa.t, y, 2*nv1   + ybase), List([0..res-1], i -> nth(x, 2*func.at(nv1*v + i + xbase))), opts),
                   assign (vtref(isa.t, y, 2*nv1+1 + ybase), isa.t.zero()))),

           let(svloadR := isa.svload[2][res-v/2],
               chain(
                   svload (vtref(isa.t, y, 2*nv1   + ybase), List([0..v/2-1],   i -> nth(x, 2*func.at(nv1*v + i + xbase))), opts),
                   svloadR(vtref(isa.t, y, 2*nv1+1 + ybase), List([v/2..res-1], i -> nth(x, 2*func.at(nv1*v + i + xbase))), opts)))
       ),

       loop(it, o.part,
           chain(
               loop(ii, 2*nv1,
                   svload(vtref(isa.t, y, ii + ybase), List([0..v/2-1], i->nth(x, 2*func.at(ii*(v/2) + i + xbase))), opts)),
           footer))
    ),

    ##
    _VScat_pc := (self, N, n, ofs, v, y, x, xofs, opts) >> Checked(IsPosInt0Sym(n), let(isa := opts.vector.isa,
        nv1 := idiv(n, v),
	res := imod(n, v),
	i   := Ind(nv1),
        chain(loop(i, nv1,        isa.storeCont(v,   y, v*i   + ofs, imod(ofs, v), x, xofs +   i, opts)),
              Cond(res=0, skip(), isa.storeCont(res, y, v*nv1 + ofs, imod(ofs, v), x, xofs + nv1, opts)))
    )),

    # NOTE: _pcAcc is a hack
    _VScat_pcAcc := (self, N, n, ofs, v, y, x, xofs, opts) >> Checked(IsPosInt0Sym(n), let(isa := opts.vector.isa,
        nv1 := idiv(n, v),
	res := imod(n, v),
	i   := Ind(nv1),
        chain(loop(i, nv1,        isa.storeContAcc(v,   y, v*i   + ofs, imod(ofs, v), x, xofs +   i, opts)),
              Cond(res=0, skip(), isa.storeContAcc(res, y, v*nv1 + ofs, imod(ofs, v), x, xofs + nv1, opts)))
    )),

    _VGath_pc := (self, N, n, ofs, v, y, yofs, x, opts) >> Checked(IsPosInt0Sym(n), let(isa := opts.vector.isa,
        nv1 := idiv(n, v),
	res := imod(n, v),
        i   := Ind(nv1),
        chain(loop(i, nv1,        isa.loadCont(v,   y, yofs +   i, x, v*i + ofs,   imod(ofs, v), opts)),
              When(res=0, skip(), isa.loadCont(res, y, yofs + nv1, x, v*nv1 + ofs, imod(ofs, v), opts)))
    )),

    VScat_pc    := (self, o, y, x, opts) >> self._VScat_pc   (o.N, o.n, o.ofs, o.v, y, x, 0, opts),
    VScat_pcAcc := (self, o, y, x, opts) >> self._VScat_pcAcc(o.N, o.n, o.ofs, o.v, y, x, 0, opts),
    VGath_pc    := (self, o, y, x, opts) >> self._VGath_pc   (o.N, o.n, o.ofs, o.v, y, 0, x, opts),

    ##
    IxVGath_pc := (self, o, y, x, opts) >> Checked(IsPosInt0(o.n), let(
	nn := _roundup(o.n, o.v)/o.v,
	jj := Ind(),
	chain(Cond(IsPosInt0(o.k) and IsPosInt0(o.ofs) and IsPosInt0(o.N),
	     # if all values are constant, we fully unroll to keep alignments constant
	     List([0..o.k-1], jj -> self._VGath_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, nn*jj, x, opts)),
	     # no need to fully unroll, alignment will still be unknown
	     loop(jj, o.k,  	    self._VGath_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, nn*jj, x, opts))))
    )),
    IxVScat_pc := (self, o, y, x, opts) >> Checked(IsPosInt0(o.n), let(
	nn := _roundup(o.n, o.v)/o.v,
	jj := Ind(),
	chain(Cond(IsPosInt0(o.k) and IsPosInt0(o.ofs) and IsPosInt0(o.N),
	     # if all values are constant, we fully unroll to keep alignments constant
	     List([0..o.k-1], jj -> self._VScat_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, x, nn*jj, opts)),
	     # no need to fully unroll, alignment will still be unknown
	     loop(jj, o.k,  	    self._VScat_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, x, nn*jj, opts))))
    )),
    ##
    _RCVGath_pc := (self, N, n, ofs, v, y, yofs, x, opts) >> let(
        nv1 := idiv(n, v), res := imod(n, v), isa := opts.vector.isa,
        chain(self._VGath_pc(2*N, 2*n, 2*ofs, v, y, 2*yofs, x, opts),
	      When(res > v/2, skip(),
		   assign(vtref(isa.t, y, 2*yofs + 2*nv1 + 1), isa.t.zero()))) # extra 0 pad is needed
    ),
    _RCVScat_pc := (self, N, n, ofs, v, y, x, xofs, opts) >> let(
        nv1 := idiv(n, v), res := imod(n, v),
        self._VScat_pc(2*N, 2*n, 2*ofs, v, y, x, 2*xofs, opts) # no extra 0 pads unlike in _RCVGath_pc case
    ),
    ##
    IxRCVGath_pc := (self, o, y, x, opts) >> Checked(IsPosInt0(o.n), let(
	nn := _roundup(o.n, o.v)/o.v,
	jj := Ind(),
	Cond(IsPosInt0(o.k) and IsPosInt0(o.ofs) and IsPosInt0(o.N),
	     # if all values are constant, we fully unroll to keep alignments constant
	     chain(List([0..o.k-1], jj -> self._RCVGath_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, nn*jj, x, opts))),
	     # no need to fully unroll, alignment will still be unknown
	     loop(jj, o.k,                self._RCVGath_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, nn*jj, x, opts)))
    )),
    IxRCVScat_pc := (self, o, y, x, opts) >> Checked(IsPosInt0(o.n), let(
	nn := _roundup(o.n, o.v)/o.v,
	jj := Ind(),
	Cond(IsPosInt0(o.k) and IsPosInt0(o.ofs) and IsPosInt0(o.N),
	     # if all values are constant, we fully unroll to keep alignments constant
	     chain(List([0..o.k-1], jj -> self._RCVScat_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, x, nn*jj, opts))),
	     # no need to fully unroll, alignment will still be unknown
	     loop(jj, o.k,                self._RCVScat_pc(o.N, o.n, o.ofs + o.N*jj, o.v, y, x, nn*jj, opts)))
    )),

    # ---------------
    # Scatter
    # ---------------
    VScat := (self, o, y, x, opts) >> Cond(IsUnalignedPtrT(y.t),
        self(VScat_u(fTensor(o.func, fBase(o.v, 0)), o.v), y, x, opts),
        self(VTensor(Scat(o.func), o.v), y, x, opts)),

    VScat_u := (self, o, y, x, opts) >> let(
        j    := Ind(o.func.domain()),
	yofs := o.func.at(j),
        loop(j, j.range,
            opts.vector.isa.storeCont(o.v, y, yofs, imod(yofs, o.v), x, j, opts))),

    VScatAcc := (self, o, y, x, opts) >>
	Cond(IsUnalignedPtrT(y.t),
             let(j      := Ind(o.func.domain()),
		 yofs   := o.func.at(j),
		 loop(j, j.range,
		     opts.vector.isa.storeContAcc(o.v, y, o.v*yofs, 0, x, j, opts))),
	     # else
	     self._acc(self(VScat(o.func, o.v),y,x,opts), y)),

    VScatAcc_u := (self, o, y, x, opts) >> let(
	j      := Ind(o.func.domain()),
	yofs   := o.func.at(j),
	nfunc  := fTensor(o.func, fBase(o.v, 0)),
	loop(j, j.range,
	    opts.vector.isa.storeContAcc(o.v, y, yofs, imod(yofs, o.v), x, j, opts))),

    VScat_zero := (self, o, y, x, opts) >> let(
	i := Ind(), j := Ind(), v := o.v, isa := opts.vector.isa,
        chain(
            loop(i, o.n,     assign(vtref(isa.t, y, i    ),  vtref(isa.t, x, i))),
            loop(j, o.N-o.n, assign(vtref(isa.t, y, o.n+j),  isa.t.zero())))),

    VStretchScat := (self, o, y, x, opts) >> let(
        v := o.v,
        func := o.func.lambda(),
        rat := func.domain() / o.part,
	nv1 := idiv(rat, v),
	res := EvalScalar(rat - v*nv1),
        ii := Ind(),
	it := Ind(),
	xbase := rat * it,
	ybase := it * ((_roundup(rat, v) / v)),
	isa := opts.vector.isa,
        svstore := isa.svstore[1][v],

        footer := Cond(
            res=0, skip(),
            let(svstoreR := isa.svstore[1][res],
                svstoreR(List([0..res-1], i->nth(y, func.at(nv1*v + i + xbase))), vtref(isa.t, x, nv1 + ybase), opts))
        ),
        loop(it, o.part,
            chain(
                loop(ii, nv1,
                    svstore(List([0..v-1], i->nth(y, func.at(v*ii + i + xbase))), vtref(isa.t, x, ii + ybase), opts)),
                footer)
        )
    ),

    vRCStretchScat := (self, o, y, x, opts) >> let(
        v := o.v/2,
        func := o.func.lambda(),
        rat := func.domain() / o.part,
	nv1 := idiv(rat, v),
	res := EvalScalar(rat-v*nv1),
        ii := Ind(),
	it := Ind(),
	ybase := it * (_roundup(rat, v) / v),
	xbase := rat * it,
	isa := opts.vector.isa,
        svstore := isa.svstore[2][v],

        footer := Cond(
            res=0, skip(),
            let(svstoreR := isa.svstore[2][res],
                svstoreR(List([0..res-1], i -> nth(y, 2 * func.at(nv1*v + i + xbase))), vtref(isa.t, x, nv1 + ybase), opts))
        ),
        loop(it, o.part,
            chain(
                loop(ii, nv1,
                    svstore(List([0..v-1], i -> nth(y, 2 * func.at(v*ii + i + xbase))), vtref(isa.t, x, ii + ybase), opts)),
                footer)
        )
    ),

    RCVStretchScat := (self, o, y, x, opts) >> let(
        v    := o.v,
        func := o.func.lambda(),
        rat  := func.domain()/o.part,
        nv1  := idiv(rat, v),
        res  := EvalScalar(rat-v*nv1),
        ii := Ind(),
        it := Ind(),
        ybase := 2 * (_roundup(rat, v) / v) * it,
	xbase := rat * it,
	isa := opts.vector.isa,
        svstore := isa.svstore[2][v/2],

        footer := Cond(
            res=0,
                skip(),

            res <= v/2, let(svstoreR := isa.svstore[2][res],
                svstoreR(List([0..res-1], i->nth(y, 2 * func.at(nv1*v + i + xbase))), vtref(isa.t, x, 2*nv1 + ybase), opts)),

            let(svstoreR := isa.svstore[2][res-v/2],
                chain(
                  svstore (List([0..v/2-1],   i->nth(y, 2 * func.at(nv1*v + i + xbase))), vtref(isa.t, x, 2*nv1   + ybase), opts),
                  svstoreR(List([v/2..res-1], i->nth(y, 2 * func.at(nv1*v + i + xbase))), vtref(isa.t, x, 2*nv1+1 + ybase), opts))
            )
        ),
        loop(it, o.part,
            chain(
                loop(ii, 2*nv1,
                    svstore(List([0..v/2-1], i->nth(y, 2 * func.at(v/2*ii + i + xbase))), vtref(isa.t, x, ii + ybase), opts)),
            footer)
        )
    ),

    # ---------------
    # Diagonal
    # ---------------
    VDiag := (self, o, y, x, opts) >>
        self(VTensor(Diag(VData(o.element, o.v)), o.v), y, x, opts),

    VDiag_x_I := (self,o,y,x,opts) >>
        self(VTensor(Diag(o.element), o.v), y, x, opts),

    # need to do VRef into the twiddles
    VRCDiag := (self, o, y, x, opts) >>
        self(VTensor(RCDiag(o.element), o.v), y, x, opts),

    VBlk := (self, o, y, x, opts) >> self(VTensor(Blk(o.velement()), o.v), y, x, opts),

    RCVBlk := (self, o, y, x, opts) >> let(
        #self(BlockMat(MapMat(o.velement(), el->RCVDiag(el, o.v))), y, x, opts),
        isa := opts.vector.isa,
        cx_mul := isa.mul_cx(opts),
        j   := Ind(),
        i   := Ind(),
        t   := TempVar(isa.t),
        u   := TempVar(isa.t),
        mat := o.velement(),
        d   := Dat(mat.t),
	data(d, mat,
            loop(j, Rows(o) / o.v,
		decl([t, u], chain( # FF: fixed for v>2
                    assign(t, 0),
                    loop(i, Cols(o) / o.v, chain(
			cx_mul(u, vtref(isa.t, x, i), nth(nth(d, j), i)),
			assign(t, add(t, u)))
		    ),
                    assign(vtref(isa.t, y, j), t)
		))
    ))),

    # dv := VData(o.element, o.v);
    RCVDiag := (self, o, y, x, opts) >> let(
        i     := Ind(),
        isa   := opts.vector.isa,
        cxmul := isa.mul_cx(opts),
        loop(i, Rows(o)/o.v,
             cxmul(vtref(isa.t, y, i), vtref(isa.t, x, i), o.element.at(i)))
    ),

    RCVOnlineDiag := (self, o, y, x, opts) >> let(
        isa        := opts.vector.isa,
        cxmul      := isa.mul_cx(opts),
        cxmul_conj := isa.mul_cx_conj(opts),
	cc         := o.element.computeWithOps(cxmul, cxmul_conj),
	decl(cc.values, chain(
	    cc.code,
	    List([1..Length(cc.values)], i -> cxmul(vtref(isa.t, y, i-1), cc.values[i], vtref(isa.t, x, i-1)))
	))
    ),

    RCVDiagSplit := (self, o, y, x, opts) >> let(
        isa := opts.vector.isa,
        i   := Ind(),
        yy  := vtref(isa.t, y, i),
        xx  := vtref(isa.t, x, i),
        t   := var.fresh_t("U", isa.t),
        t2  := var.fresh_t("U", isa.t),
        loop(i, Rows(o)/o.v,
	    decl([t, t2], chain(
                isa.swap_cx(t, xx, opts),
                assign(yy, add(mul(o.element.at(2*i), xx), mul(o.element.at(2*i+1), t))))))
    ),

    VS := meth(self, o, y, x, opts)
        local xv, yv, i, n, isa, u, w;
	isa := opts.vector.isa;
        xv := tcast(TPtr(TVect(x.t.t, o.v)), x);
        yv := tcast(TPtr(TVect(y.t.t, o.v)), y);
        n := o.n / o.v;
        i := Ind();
	u := var.fresh_t("U", TVect(x.t.t, o.v));
        if o.transposed then
	    return decl([u], chain(
		loop(i, n-1, chain(
		    isa.bin_shl2(u, [nth(xv,i), nth(xv, i+1)], opts),
                    assign(nth(yv, i+1), add(u, nth(xv, i+1))))
		),
		isa.bin_shl1(u, nth(xv, 0), opts),
                assign(nth(yv, 0), add(u, nth(xv, 0)))
	    ));
        else
	    return decl([u], chain(
                loop(i, n-1, chain(
                    isa.bin_shr2(u, [nth(xv, i), nth(xv, i+1)], opts),
                    assign(nth(yv, i), add(u, nth(xv, i))))
		),
                isa.bin_shr1(u, nth(xv, n-1), opts),
                assign(nth(yv, n-1), add(u, nth(xv, n-1)))));
        fi;
    end,

    VO1dsJ := meth(self, o, y, x, opts)
        local xv, yv, i, n, isa, t;
        isa := opts.vector.isa;
        t   := Checked( IsVecT(isa.t) and isa.t.size = o.v, TPtr(isa.t));
        xv  := tcast(t, x);
        yv  := tcast(t, y);
        n   := o.n / o.v;
	i   := Ind(n-1);
	return chain(
	    loop(i, n-1, isa.bin_shrev(nth(yv, i+1), [nth(xv, n-i-1), nth(xv, n-i-2)], opts)),
            isa.bin_shrev(nth(yv, 0), [isa.t.zero(), nth(xv, n-1)], opts));
    end,

    VScale := (self, o, y, x, opts) >>
        self(VDiag(fConst(TReal, Rows(o), o.s), o.v) * o.child(1), y, x, opts),

    VOLMultiplication := (self, o, y, x, opts) >>
        self(VTensor(OLMultiplication(o.rChildren()[1], o.rChildren()[2]),  o.rChildren()[3]), y, x, opts),

    VRCOLMultiplication := (self, o, y, x, opts) >>
        self(VTensor(RCOLMultiplication(o.rChildren()[1], o.rChildren()[2]), o.rChildren()[3]), y, x, opts),

    RCVOLMultiplication := (self, o, y, x, opts) >> let(
        n  := o.rChildren()[2],
        m  := o.rChildren()[1],
        v  := o.rChildren()[3],
        t  := TPtr(opts.vector.isa.t),
        xv := List(x, e -> tcast(t, e)),
        yv := tcast(t, StripList(y)),
        mul_cx := opts.vector.isa.mul_cx(opts),
        i  := Ind(),
        a  := var.fresh_t("a", opts.vector.isa.t),
        loop( i, n, decl([a], chain(
            assign(a, nth(xv[1], i)),
            chain( List( [2..m], j -> mul_cx(a, nth(xv[j], i), a) )),
            assign(nth(yv, i), a))))
    ),

    VOLConjMultiplication := (self, o, y, x, opts) >>
        self(VTensor(OLConjMultiplication(o.rChildren()[1], o.rChildren()[2]),  o.rChildren()[3]), y, x, opts),

    VRCOLConjMultiplication := (self, o, y, x, opts) >>
        self(VTensor(RCOLConjMultiplication(o.rChildren()[1], o.rChildren()[2]), o.rChildren()[3]), y, x, opts),

    RCVOLConjMultiplication := (self, o, y, x, opts) >> let(
        n  := o.rChildren()[2],
        m  := o.rChildren()[1],
        v  := o.rChildren()[3],
        t  := TPtr(opts.vector.isa.t),
        xv := List(x, e -> tcast(t, e)),
        yv := tcast(t, StripList(y)),
        mul_cx_conj := opts.vector.isa.mul_cx_conj(opts),
        i  := Ind(),
        a  := var.fresh_t("a", opts.vector.isa.t),
        loop( i, n, decl([a], chain(
            assign(a, nth(xv[1], i)),
            chain( List( [2..m], j -> mul_cx_conj(a, nth(xv[j], i), a) )),
            assign(nth(yv, i), a))))
    ),

    RCVIxJ2 := (self, o, y, x, opts) >> let(
        isa := opts.vector.isa,
        t   := Checked( IsVecT(isa.t) and isa.t.size = o.v, TPtr(isa.t)),
        xv  := tcast(t, x),
        yv  := tcast(t, y),
        opts.vector.isa.RCVIxJ2(nth(yv,0), nth(xv, 0), opts)),

    VIxJ2 := (self, o, y, x, opts) >> let(
        isa := opts.vector.isa,
        t := Checked(IsVecT(isa.t) and isa.t.size = o.v, TPtr(isa.t)),
        xv := tcast(t, x),
        yv := tcast(t, y),
        opts.vector.isa.swap_cx(nth(yv, 0), nth(xv, 0), opts) ),

    VJxI := (self, o, y, x, opts) >> let(
        isa := opts.vector.isa,
        t   := Checked( IsVecT(isa.t) and isa.t.size = o.v, TPtr(isa.t)),
        xv  := tcast(t, x),
        yv  := tcast(t, y),
        opts.vector.isa.VJxI(o.m, nth(yv,0), nth(xv, 0), opts)),

));


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

Declare(DContainer);

##############################################################################
Class(DContainer, VRC, rec(
    toAMat := (self) >> AMatMat(MatSPL(self.child(1))),
    new := meth(self, spl, procs)
        local res;
        res := SPL(WithBases(self, rec(_children:=[spl], procs:=procs,
                                dimensions := spl.dimensions)));
        res.dimensions := res.dims();
        return res;
    end,
    print := (self, i, is) >> Print(self.name, "(", self.child(1).print(i+is,is), ", ", self.procs, ")"),
    unroll := self >> self,
    transpose := self >> DContainer(self.child(1).transpose(), self.procs),
    dims := self >> self.child(1).dims()
));


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

    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 64: A_2A_1
#
handle64char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[64];
    info:=infos[64];

    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 60: A_2^2
#
handle60char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[60];
    info:=infos[60];

    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 57: D_4(a_1)
#
handle57char5:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[57];
    info:=infos[57];

    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 53: D_4(a_1)A_1 Ross' rep
#
handle53char5:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[53];
    info:=infos[53];

    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 52: A_3A_2 [3,4,2,6,7]
#
handle52char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[52];
    info:=infos[52];

    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 51: A_4 [1,2,3,4]
#
handle51char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[51];
    info:=infos[51];

    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,3],[2,2],[10,2],[11,-1],[17,2]],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,3],[2,2],[10,2],[11,-1],[17,2]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,3,4],[-1,-1,-1,-1]);

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

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

    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 47: A_4A_1
#
handle47char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[47];
    info:=infos[47];

    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,2],[2,2],[10,3],[11,-1],[17,2]],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,2],[2,2],[10,3],[11,-1],[17,2]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2,3,4],[-1,-1,-1,-1]);

    return [tmp1,tmp2];
end;
#vals[2]:=vals[1]+2*One(APR);
#vals[3]:=-(-vals[1]+One(APR));
#vals[4]:=-(vals[2]+One(APR));
#vals[10]:=-(vals[1]^2+2*vals[1]-vals[9]+3*One(APR));
#vals[11]:=-(-vals[1]+vals[9]+One(APR));
#vals[17]:=-(-vals[1]^2+3*vals[1]+vals[16]+3*One(APR));
#
# nu e gata dar am gasit elementul

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

    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 44: A_4A_1^2
#
handle44char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[44];
    info:=infos[44];

    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),[[3,1],[4,3],[2,3],[10,3],[11,-1],[17,3]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2,7],[-1,-1]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[71,72,112,111]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,1],[4,3],[2,3],[10,3],[11,-1],[17,3]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2,7],[-1,-1]);

    return [tmp1,tmp2];
end;
#vals[2]:=-(-vals[1]+2*One(APR));
#vals[3]:=vals[1]+One(APR);
#vals[4]:=-vals[2]+One(APR);
#vals[10]:=-vals[1]^2+2*vals[1]+vals[9]+2*One(APR);
#vals[11]:=-vals[1]-vals[9]-One(APR);
#vals[17]:=-vals[1]^2+2*vals[1]-vals[16]+3*One(APR);
#
# nu e gata dar am gasit elementul

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

    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,2],[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,2],[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]+3*One(APR));
#vals[4]:=-(vals[2]+One(APR));
#vals[5]:=vals[4];
#vals[8]:=-vals[7]-One(APR);
#vals[11]:=-(vals[2]*vals[3]+vals[10]+One(APR));
#vals[12]:=-(-vals[2]+vals[10]-One(APR));
#
# nu e gata dar am gasit elementul

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

    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 33: D_6(a_2)
#
handle33char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[33];
    info:=infos[33];

    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 32: E_6(a_3)A_1 [1,4,6,8,17,18,19] this doesn't work in char2
#
handle32char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[32];
    info:=infos[32];

    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 31: E_7(a_5)
#
handle31char5:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=Classes(orbs)[31];
    info:=infos[31];

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

    tmp3:=ConjugateByTori(rep,[2,3,5],[bbb,bbb,bbb]);

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

    tmp4:=0;
    tmp4:=ConjugateByTori(cent,[2,3,5],[bbb,bbb,bbb]);

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

#
# cls 29: E_8(a_7)                           # NOT FINISHED IN CHARACTERISTIC 5
#
handle29char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[29];
    #info:=infos[29];

    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 27: D_6(a_1)
#
handle27char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[27];
    info:=infos[27];

    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 25: E_7(a_4) [1,4,7,14,17,18,19,26] this doesn't work in char2
#
handle25char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent;
    o:=Classes(orbs)[25];
    info:=infos[25];

    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 24: E_6(a_1)
#
handle24char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[24];
    info:=infos[24];

    #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],[11,-1],[12,3],[10,-1],[5,2],[6,3],[13,3],[18,-1],[19,-1],[20,2],[26,2],[27,2],[32,1],[40,3],[48,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2],[-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],[11,-1],[12,3],[10,-1],[5,2],[6,3],[13,3],[18,-1],[19,-1],[20,2],[26,2],[27,2],[32,1],[40,3],[48,-1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2],[-1,-1]);

    return [tmp1,tmp2,uu];
end;
#vals[2]:=vals[1]+One(APR);
#vals[4]:=0;
#vals[3]:=0;
#vals[5]:=-(-vals[1]+3*One(APR));
#vals[6]:=vals[5]+One(APR);
#vals[10]:=-(-vals[1]+vals[5]-One(APR));
#vals[11]:=-vals[1]-One(APR);
#vals[12]:=-vals[2]+vals[10];
#vals[13]:=-(2*vals[1]^2+2*vals[9]+2*One(APR));
#vals[16]:=-(2*One(APR))^-1*(vals[1]^2+vals[1]-vals[9]);
#vals[17]:=-vals[1]^2-vals[1]-vals[16];
#vals[18]:=-(3*vals[1]^2+3*vals[1]+2*vals[9]-One(APR));
#vals[19]:=vals[1]-vals[16]+One(APR);
#vals[20]:=vals[1]^2+3*vals[1]+vals[18]+2*One(APR);
#
#vals[24]:=-(3*One(APR))^-1*(vals[1]^3+3*vals[1]*vals[9]-vals[1]+vals[23]);
#vals[25]:=vals[1]^3-vals[1]*vals[9]-vals[1]+2*vals[9]+vals[23]-vals[24];
#vals[26]:=-(2*vals[1]^2-vals[1]-vals[9]+vals[23]-One(APR));
#vals[27]:=-vals[1]^3+vals[1]*vals[9]+2*vals[1]+2*vals[9]+vals[24]+One(APR);
#
#vals[30]:=-vals[1]^4+3*vals[1]^3+2*vals[1]^2*vals[9]-vals[1]^2+2*vals[1]*vals[9]-vals[9]^2-vals[23];
#vals[32]:=vals[1]^3+2*vals[1]^2+vals[9]+vals[23]-One(APR);
#vals[33]:=2*vals[1]^4+vals[1]^3-vals[1]^2*vals[9]+2*vals[1]*vals[9]+2*vals[1]-vals[23]+vals[24]+vals[25]-vals[27]+vals[30]-vals[31]+One(APR);
#vals[37]:=-(vals[1]^4+3*vals[1]^3+2*vals[1]^2*vals[9]+vals[1]^2-vals[1]*vals[9]+vals[9]^2-vals[1]-vals[9]+2*vals[23]+vals[31]);
#
#vals[40]:=2*vals[1]^4-vals[1]^3+3*vals[1]^2*vals[9]+3*vals[1]*vals[9]+2*vals[1]-vals[9]+vals[37]-One(APR);
#vals[44]:=-(vals[1]^4+3*vals[1]^3*vals[9]+2*vals[1]^3+2*vals[1]^2*vals[9]+2*vals[1]*vals[9]^2+vals[1]^2-vals[1]*vals[9]+vals[23]-vals[38]);
#vals[48]:=3*vals[1]^5+3*vals[1]^3*vals[9]+3*vals[1]^3+vals[1]^2*vals[9]+vals[1]*vals[9]+vals[9]^2+2*vals[1]+2*vals[9]+vals[23]+vals[37]-vals[38]+vals[45]+3*One(APR);
#
#vals[51]:=-(3*vals[1]^3*vals[9]+2*vals[1]^3*vals[23]-vals[1]^2*vals[9]^2+3*vals[1]^2*vals[9]-vals[1]*vals[23]-vals[9]^2+2*vals[9]*vals[23]-vals[23]^2+vals[38]);
#
# nu e gata dar am gasit elementul

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

    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 20: D_7(a_2)
#
handle20char5:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=Classes(orbs)[20];
    info:=infos[20];

    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;

#
# cls 18: E_6(a_1)A_1
#
handle18char5:=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,2],[5,3],[10,-1],[6,2],[13,3],[18,-1],[19,-1],[20,2],[26,1],[27,3],[32,1],[40,2]],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,2],[5,3],[10,-1],[6,2],[13,3],[18,-1],[19,-1],[20,2],[26,1],[27,3],[32,1],[40,2]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[1,2,3,5,6],[-1,-1,-1,-1,-1]);

    return [tmp1,tmp2,uu];
end;
#vals[2]:=-(vals[1]-One(APR));
#vals[4]:=0;
#vals[3]:=0;
#vals[5]:=vals[1]+3*One(APR);
#vals[6]:=-(-vals[5]+One(APR));
#vals[10]:=-(vals[1]-vals[5]-One(APR));
#vals[11]:=-(vals[1]-One(APR));
#vals[12]:=vals[2]-vals[10];
#vals[13]:=-(2*One(APR))^-1*(-vals[1]^2-vals[9]-One(APR));
#
#vals[16]:=3*vals[1]+vals[9]-vals[13]+3*One(APR);
#vals[17]:=-(-vals[1]^2+vals[1]-vals[16]);
#vals[18]:=-vals[16]-One(APR);
#vals[19]:=-(-vals[1]^2+2*vals[1]+vals[9]-vals[16]-One(APR));
#vals[20]:=vals[1]^2+2*vals[1]-vals[18]+2*One(APR);
#vals[23]:=-(3*One(APR))^-1*(vals[1]^3+2*vals[1]^2+3*vals[1]*vals[9]-vals[1]+2*vals[9]+vals[13]+2*One(APR));
#vals[24]:=-(3*One(APR))^-1*(3*vals[1]^3-vals[1]*vals[9]+2*vals[1]);
#vals[25]:=-(vals[1]^3+vals[1]^2-vals[1]*vals[9]-vals[1]+vals[9]-vals[16]+vals[17]+vals[19]-vals[23]-vals[24]-One(APR));
#vals[26]:=-vals[1]+3*vals[9]+vals[13]-vals[23]+3*One(APR);
#vals[27]:=-(vals[1]^3-vals[1]*vals[9]+3*vals[1]+2*vals[9]-vals[24]+One(APR));
#
#vals[30]:=-(2*vals[1]^3-vals[1]^2*vals[9]+vals[1]^2*vals[13]+vals[1]^2+3*vals[1]*vals[9]+vals[9]*vals[13]-vals[13]^2+2*vals[9]+vals[13]-vals[23]+One(APR));
#vals[32]:=-(vals[1]^3+3*vals[1]^2-vals[9]-vals[23]+One(APR));
#vals[33]:=-(-vals[1]^4+vals[1]^3+vals[1]^2*vals[9]+vals[1]^2*vals[13]+2*vals[1]^2+2*vals[1]*vals[9]-vals[1]*vals[13]+2*vals[1]+2*vals[9]+vals[27]-vals[30]-vals[31]+One(APR));
#vals[37]:=vals[1]^4+3*vals[1]^3+2*vals[1]^2*vals[9]+vals[1]^2-vals[1]*vals[9]+vals[9]^2-vals[9]-vals[23]+vals[31];
#
#vals[40]:=-(2*vals[1]^3+vals[1]^2*vals[9]-vals[1]^2*vals[13]-vals[1]^2+3*vals[1]*vals[9]+3*vals[1]*vals[13]-vals[1]+3*vals[13]-vals[37]);
#
#vals[44]:=-vals[1]^4+3*vals[1]^3*vals[9]-vals[1]^3+3*vals[1]^2*vals[9]+2*vals[1]*vals[9]^2-vals[1]^2+3*vals[1]+3*vals[23]+vals[38];
#
#vals[48]:=-(3*vals[1]^5+3*vals[1]^3*vals[9]-vals[1]^2*vals[9]+2*vals[1]*vals[9]-vals[9]^2+3*vals[9]+3*vals[23]+vals[37]+vals[38]-vals[45]+2*One(APR));
#
#vals[51]:=-vals[1]^6+2*vals[1]^4*vals[9]-vals[1]^4+3*vals[1]^3*vals[9]+2*vals[1]^3*vals[23]+3*vals[1]^3*vals[24]-vals[1]^2*vals[9]^2+2*vals[1]^2*vals[9]+3*vals[1]*vals[9]^2+3*vals[1]*vals[9]*vals[23]-vals[1]*vals[9]*vals[24]+2*vals[1]^2-vals[1]*vals[9]+vals[1]*vals[23]+2*vals[1]*vals[24]-vals[9]^2-vals[9]*vals[23]+3*vals[23]*vals[24]-vals[24]^2+vals[38];
#
# nu e gata dar am gasit elementul

#
# 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
#
handle17char5:=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;

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

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[1,2,5]);
    uu:=Unipotent(chevalleyAdj(rep),[[23,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,3,4,8],[-1,-1,-1,-1]);

    tmp3:=ConjugateByTori(rep,[1,2,5],[bbb,bbb,bbb^2]);

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

    tmp4:=0;
    tmp4:=ConjugateByTori(cent,[1,2,5],[bbb,bbb,bbb^2]);

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

#
# cls 15: D_7(a_1)
#
handle15char5:=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],[3*One(APR),3*One(APR),-1]);
    #tmp1:=ConjugateByTori(tmp1,[2,3,4],[ccc,ccc,-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],[3*One(APR),3*One(APR),-1]);
    #tmp2:=ConjugateByTori(tmp2,[2,3,4],[ccc,ccc,-1]);

    return [tmp1,tmp2];
end;

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

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[2,3,8,13]);
    uu:=Unipotent(chevalleyAdj(rep),[[18,-1],[29,-1],[31,1],[33,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[2,3,4,5,7,8],[-1,-1,-1,-1,-1,-1]);

    #tmp3:=ConjugateByTori(rep,[2,3,5,6,8],[aaa,aaa,aaa,aaa,aaa^2]);
    tmp3:=ConjugateByTori(rep,[2,3,5,6,8],[bbb,bbb,bbb,bbb,bbb^2]);

    tmp2:=0;
    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[2,3,8,13]);
    uu:=Unipotent(chevalleyAdj(cent),[[18,-1],[29,-1],[31,1],[33,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[2,3,4,5,7,8],[-1,-1,-1,-1,-1,-1]);

    tmp4:=0;
    tmp4:=ConjugateByTori(cent,[2,3,5,6,8],[bbb,bbb,bbb,bbb,bbb^2]);

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

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

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[1,2,5]);
    uu:=Unipotent(chevalleyAdj(rep),[[19,-1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[1,3,4],[-1,-1,-1]);

    tmp3:=ConjugateByTori(rep,[1,2,5],[bbb,bbb,bbb^2]);

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

    tmp4:=0;
    tmp4:=ConjugateByTori(cent,[1,2,5],[bbb,bbb,bbb^2]);

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

# For representative deduced from Seitz' E_8(a_6) used in proof
#
# cls 8: E_8(a_5)
#
handle8char5:=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
#
handle7char5:=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
#
handle5char5:=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
#
handle4char5:=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;