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

# helps translate VTensorInd(Scat(f), i) -> VScat_sv(f \tensor_i fId(i.range))
_fTensorInd := function(fnc, idx)
    local ds, irng, fdom, nidx, lambda;
    irng := idx.range;

    if not (idx in fnc.free()) then
        return fTensor(fnc, fId(irng));
    fi;

    fdom := fnc.domain();
    nidx := Ind(irng*fdom);

    lambda := Lambda(nidx, imod(nidx, irng)+V(irng)*Lambda(idx, fnc.at(idiv(nidx, irng))).at(imod(nidx, irng))).setRange(V(fnc.range())*V(irng));
    return lambda;
end;

#prevent VRC Rules from introducing VRCL/VRCR in the wrong spot
DontNeedSpecialVRC := e->not ForAny(e.children(), f->IsBound(f.needSpecialVRC) and f.needSpecialVRC());

# Test for VRCs that, for any construct, has a needSeparateVRC set
anyNeedSeparateVRC := e->ForAny(e.children(), f->IsBound(f.needSeparateVRC) and f.needSeparateVRC());

# Test for VRCs that for all constructs, cannot change data formats
TotallyNonChangeable := function(e)
    if ObjId(e)=Compose or ObjId(e)=ComposeDists then
      return(ForAll(e.children(), f->IsBound(f.totallyCannotChangeDataFormat) and f.totallyCannotChangeDataFormat()));
    else
      return(IsBound(e.totallyCannotChangeDataFormat) and e.totallyCannotChangeDataFormat());
    fi;
end;

# Test for VRCs that have at least one construct that cannot change data formats
HasNonchangeable := function(e)
    if ObjId(e)=Compose or ObjId(e)=ComposeDists then
      return(ForAny(e.children(), f->IsBound(f.cannotChangeDataFormat) and f.cannotChangeDataFormat()));
    else
      return(IsBound(e.cannotChangeDataFormat) and e.cannotChangeDataFormat());
    fi;
end;

NoSpecialOrNonchangeableVRC := e->(DontNeedSpecialVRC(e) and not HasNonchangeable(e));
NoSpecialAndChangeableVRC   := e->(DontNeedSpecialVRC(e) and     HasNonchangeable(e));

HandleCannotChangeDataFormatVRC := function(vrc, ch, v)
    local remch, ele, left, right, i, vrc1, vrc2;

    remch := Compose(Drop(ch, 1));
    #Error("BP: cannotChangeDataFormatVRC");

    if vrc=VRC then
        # NOTE: How do we handle this?
        # If ALL children cannot change data format, simply distribute the VRC amongst them

        if TotallyNonChangeable(Compose(ch)) then
            return( Compose(VRC(ch[1], v), VRC(remch,v)) );
        fi;

        # if any of the children NOTE: this is inefficient: what we really
        # should to is to bunch together the children that needSeparateVRC, and
        # the children that are regular, and handle them separately. For now,
        # this hack works because we should have no cases where such children
        # will be mixed together.

        if anyNeedSeparateVRC(Compose(ch)) then
            return( Compose(VRC(ch[1], v), VRC(remch,v)) );
        fi;

        # If not, we must do a VRCR/VRCL split
        # Find the rightmost child that can change dataformats. That and
        # everything right of that becomes a VRCL. Everything left of that is a VRCR.

        # Bad (but good:)) HACK: for things of the form S*A*G, where S and G
        # cannot change data formats, stuff the VRC into A, and hope that A is
        # big enough to handle it.

        if Length(ch)=3 and TotallyNonChangeable(ch[1]) and TotallyNonChangeable(ch[3]) then
          return(Compose( VRC(ch[1], v), VRC(ch[2], v), VRC(ch[3], v) ));
        fi;

        for i in Reversed([1..Length(ch)]) do
          if not TotallyNonChangeable(ch[i]) then
            # We found the rightmost child
            #Error("VRC: BP");
            left  := Compose(List([1..(i-1)], e->ch[e]));
            right := Compose(List([i..Length(ch)], e->ch[e]));
            #Error("VRC: BP");
            return( Compose( VRCR(left, v), VRCL(right, v) ) );
          fi;
        od;

        #Error("BP: cannotChangeDataformatVRC: VRC needs to be split into VRCL/VRCR");
    fi;

    if vrc=VRCLR then
        # This should really be handled as if the cannotChangeDataformat doesn't exist.
        #Error("VRCLR: BP");
        vrc1 := [[VRCL, VRCR], [VRCLR, VRCLR]];
        vrc2 := When(NeedInterleavedRight(ch[1]) or NeedInterleavedLeft(ch[2]), vrc1[1], vrc1[2]);
        #Error("VRCLR: BP");
        return( Compose(vrc2[1](ch[1], v), vrc2[2](Compose(Drop(ch, 1)), v)) );
    fi;

    if vrc=VRCL then
        # Find the first child from the right that is capable of making a
        # format change. We then have 2 cases.

        # Note: we really have 3 cases to be general: VRCL -> (VRCLR, VRCL, VRC)
        # Sometimes, the LR won't exist, and sometimes, the VRC won't exist
        # But it's easier to code if we only always break down to 2 cases

        for i in Reversed([1..Length(ch)]) do
          if not TotallyNonChangeable(ch[i]) then
            # We found the child that will do the dataformat change (VRCL)
            # We now have 2 cases.
            #   Case 1: VRCL -> (VRCL,  VRC)   (found child is not rightmost)
            #   Case 2: VRCL -> (VRCLR, VRCL)  (found child is rightmost)

            if i <> Length(ch) then # Case 1
              left  := Compose(List([1..i],              e->ch[e]));
              right := Compose(List([(i+1)..Length(ch)], e->ch[e]));
              #Error("BP:VRCL1\n, ---------VRCL(left)---------", left, "\n---------VRC(right)--------", right);
              return( Compose( VRCL(left, v), VRC(right, v) ) );
            else                    # Case 2
              left  := Compose(List([1..(i-1)], e->ch[e]));
              right := ch[i];
              #Error("BP:VRCL2\n, ---------VRCLR(left)---------", left, "\n---------VRCL(right)--------", right);
              return( Compose( VRCLR(left, v), VRCL(right, v) ) );
            fi;
          fi;
        od;
        Error("cannotChangeDataFormatVRC/L: Didn't find an appropriate child");
    fi;

    if vrc=VRCR then
        # Find the first child from the left that is capable of making a
        # format change. We then have 2 cases.

        for i in [1..Length(ch)] do
          if not TotallyNonChangeable(ch[i]) then
            # We found the child that will do the dataformat change (VRCR)
            # We now have 2 cases.
            #   Case 1: VRCR -> (VRC,  VRCR)   (found child is not leftmost)
            #   Case 2: VRCR -> (VRCR, VRCLR)  (found child is leftmost)

            if i <> 1 then # Case 1
              left  := Compose(List([1..(i-1)],      e->ch[e]));
              right := Compose(List([i..Length(ch)], e->ch[e]));
              #Error("BP:VRCR1\n, ---------VRC(left)---------", left, "\n---------VRCR(right)--------", right);
              return( Compose( VRC(left, v), VRCR(right, v) ) );
            else                    # Case 2
              left  := ch[i];
              right := Compose(List([(i+1)..Length(ch)], e->ch[e]));
              #Error("BP:VRCR2\n, ---------VRCR(left)---------", left, "\n---------VRCLR(right)--------", right);
              return( Compose( VRCR(left, v), VRCLR(right, v) ) );
            fi;
          fi;
        od;
        Error("cannotChangeDataFormatVRC/R: Didn't find an appropriate child");

    fi;

    Error("cannotChangeDataFormat: I don't know what to do with this one. None of the cases matched.");

#            vrc=VRCL and     HasNonchangeable(ch1) and not HasNonchangeable(ch2), Compose(VRCLR(ch1, v), VRCL(remch, v) ),
#            vrc=VRCL and not HasNonchangeable(ch1) and     HasNonchangeable(ch2), Compose(VRCL(ch1, v),  VRC(remch,v)   ),
#            vrc=VRCL and     HasNonchangeable(ch1) and     HasNonchangeable(ch2),
#                When(NeedInterleavedRight(ch1) or NeedInterleavedLeft(ch2), Compose(VRCL(I(ch1.dims()[1]),v),  VRC(ch,v)),
#                                                                            ComposeVRCLR(ch,v), VRCL(I(ch1.dims()[1]),v)),
#
#            vrc=VRCR and     HasNonchangeable(ch1) and not HasNonchangeable(ch2), Compose(VRC(ch1, v),   VRCR(remch, v) ),
#            vrc=VRCR and not HasNonchangeable(ch1) and     HasNonchangeable(ch2), Compose(VRCR(ch1, v),  VRCLR(remch,v) ),
#            vrc=VRCR and     HasNonchangeable(ch1) and     HasNonchangeable(ch2),
#                When(NeedInterleavedRight(ch1) or NeedInterleavedLeft(ch2), Compose(VRCL(I(ch1.dims()[1]),v),  VRC(ch,v)),
#                                                                            ComposeVRCLR(ch,v), VRCL(I(ch1.dims()[1]),v))
#          )
#        )
#    ),

end;

Scat.needInterleavedLeft:=False;
Scat.needInterleavedRight:=False;

# NOTE: Why were these set to False???
Gath.needInterleavedLeft:=True;
Gath.needInterleavedRight:=True;

VBlk.needInterleavedLeft:=False;
VBlk.needInterleavedRight:=False;

# NOTE: remove Diag here
Diag.needInterleavedLeft:=False;
Diag.needInterleavedRight:=False;

Inplace.needInterleavedRight := self >> self.child(1).needInterleavedRight();
Inplace.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
Inplace.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
Inplace.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

BB.needInterleavedRight := self >> self.child(1).needInterleavedRight();
BB.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
BB.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
BB.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

VContainer.needInterleavedRight := self >> self.child(1).needInterleavedRight();
VContainer.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
VContainer.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
VContainer.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

NoDiagPullin.needInterleavedRight := self >> self.child(1).needInterleavedRight();
NoDiagPullin.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
NoDiagPullin.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
NoDiagPullin.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

NoDiagPullinLeft.needInterleavedRight := self >> self.child(1).needInterleavedRight();
NoDiagPullinLeft.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
NoDiagPullinLeft.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
NoDiagPullinLeft.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

NoDiagPullinRight.needInterleavedRight := self >> self.child(1).needInterleavedRight();
NoDiagPullinRight.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
NoDiagPullinRight.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
NoDiagPullinRight.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

SymSPL.needInterleavedRight := self >> self.child(1).needInterleavedRight();
SymSPL.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
SymSPL.cannotChangeDataFormat := self >> self.child(1).cannotChangeDataFormat();
SymSPL.totallyCannotChangeDataFormat := self >> self.child(1).totallyCannotChangeDataFormat();

VGath_sv.rcVariant := RCVGath_sv;
VScat_sv.rcVariant := RCVScat_sv;

RCVGath_sv.vecVariant := (f,v)->VGath(f,v);
RCVScat_sv.vecVariant := (f,v)->VScat(f,v);

ScatGath.needInterleavedLeft:=True;
ScatGath.needInterleavedRight:=True;

SMPBarrier.needInterleavedLeft := (self) >> self.child(1).needInterleavedLeft();
SMPBarrier.needInterleavedRight := (self) >> self.child(1).needInterleavedRight();

_VRCFamily := [VRC, VRCL, VRCR, VRCLR];

Class(RulesVRC, RuleSet);
RewriteRules(RulesVRC, rec(
    VRC_VContainer := Rule([@(1, _VRCFamily), @(2, VContainer)],
	e -> let(cont := @(2).val,
	    VContainer(ObjId(@(1).val)(cont.child(1), @(1).val.v), cont.isa))),

    VRC_ISum := Rule([@(1, _VRCFamily), @(2, [ISum, SMPSum, SMPBarrier, SUM])], e->let(s := @(2).val,
    CopyFields(s, rec(_children := List(s.children(), c->ObjId(@(1).val)(c, @(1).val.v)),
                  dimensions := @(1).val.dimensions)))),

    VRC_Container := Rule([@(1, _VRCFamily),  @(2, [BB,Buf,Inplace,RecursStep,NoDiagPullin, NoDiagPullinLeft, NoDiagPullinRight])],
        e -> ObjId(@(2).val)(ObjId(@(1).val)(@(2).val.child(1), @(1).val.v))),

    VRC_Data := Rule([@(1, _VRCFamily), @(2, Data)],
	e -> Data(@(2).val.var, @(2).val.value, ObjId(e)(@(2).val.child(1)))),

#   VRC_SymSPL := Rule([@(1, _VRCFamily), @(2, SymSPL)],
#        e -> ObjId(@(1).val)(@(2).val.child(1), @(1).val.v)),

   VRC_Compose := Rule([@(1, _VRCFamily), @(2, [Compose,ComposeStreams]).cond(NoSpecialOrNonchangeableVRC)], # Use with VRC_ComposeNonchangeable below
        e->let(v := @(1).val.v, ch := @(2).val.children(), vrc := ObjId(@(1).val),
        vrc1 := Cond(
                 vrc=VRC,   [[VRC,  VRC ], [VRCR,  VRCL ]],
                 vrc=VRCLR, [[VRCL, VRCR], [VRCLR, VRCLR]],
                 vrc=VRCL,  [[VRCL, VRC ], [VRCLR, VRCL ]],
                 vrc=VRCR,  [[VRC,  VRCR], [VRCR,  VRCLR]]),
        vrc2 := When(NeedInterleavedRight(ch[1]) or NeedInterleavedLeft(ch[2]), vrc1[1], vrc1[2]),
            Compose(vrc2[1](ch[1], v), vrc2[2](Compose(Drop(ch, 1)), v)))),

    # NOTE: When building the format-change I() below, ch1.dims()[1] is
    #        always used. This is probably incorrect in some cases.
    VRC_ComposeNonchangeable := Rule([@(1, _VRCFamily), @(2, Compose).cond(NoSpecialAndChangeableVRC)],
        e->let(v     := @(1).val.v,
               ch    := @(2).val.children(),
               vrc   := ObjId(@(1).val),
               HandleCannotChangeDataFormatVRC(vrc, ch, v)
        )
    ),

    #VRC_ComposeStreams := Rule([@(1, VRC), @(2, ComposeStreams)],
    #    e->let(v := @(1).val.v, ch := @(2).val.children(),
    #        ComposeStreams( VRC(ch[1], v), VRC(ComposeStreams(Drop(ch,1)), v) )
    #        )
    #),

    VRCLR_VRCDiag:= Rule([@(1, _VRCFamily), @(2, VRCDiag)], e -> ObjId(@(1).val)(@(2).val.toloop().sums(), @(1).val.v)),

    VRCLR_VBlk:= Rule([@(1, VRCLR),@(2, VBlk)], e -> VBlk(RealVMatComplexVMat(@(2).val.element), @(2).val.v)),

    VRCLR_VGath_zero:= Rule([@(1, VRCLR),@(2, VGath_zero)], e -> let(g:=@(2).val,
        VRCLR(VGath(fAdd(g.N, g.n, 0), getV(g)), getV(g)))),

    VRCLR_VGathScat := Rule([@(1, VRCLR), @(2, [VGath, VScat, VScatAcc])],
        e -> ObjId(@(2).val)(fTensor(@(2).val.func, fId(2)), @(2).val.v)),

    VRCLR_GathScat := Rule([@(1, VRCLR), @(2, [Gath, Scat, ScatAcc])],
        e -> ObjId(@(2).val)(fTensor(@(2).val.func, fId(2)))),

    VRC_VGathVScat := Rule([@(1, VRC), @(2, [VGath, VScat, VScatAcc])],
        e -> ObjId(@(2).val)(fTensor(@(2).val.func, fId(2)), @(2).val.v)),

    VRCLR_VGathScat_sv := Rule([@(1, VRCLR), @(2, [VGath_sv, VScat_sv])], e->  # NOTE: acc variant missing
        @(2).val.rcVariant(@(2).val.func, @(2).val.v, @(2).val.sv, @(2).val.rem)),

    VRCLR_VTensor := Rule([@(1, VRCLR), @(2, VTensor)],
        e -> VTensor(RC(@(2).val.child(1)), @(2).val.vlen)),

    VRCLR_Diag := Rule([@(1, VRCLR), @(2, [Diag, VDiag])],
        e -> let(d := @(2).val.element, n := d.domain(), v := @(1).val.v,
            VRCDiag(VData(fCompose(RCData(d), fTensor(fId(n / v), L(2*v, 2))), v), v))),

    VRC_BlockVPerm := Rule([@(1, _VRCFamily), @(2, [BlockVPerm, BlockVPerm2])],
        e->let(bd := @(2).val.child(1), vrc := ObjId(@(1).val),
            BlockVPerm(@2.val.n, @2.val.vlen, vrc(bd, @(1).val.v),
            MatSPL(vrc(@2.val.perm, @1.val.v))))),

    fPrecompute_VData := Rule([VData, [fPrecompute, @(1)], @(2)], e -> fPrecompute(VData(@(1).val, @(2).val))),
    fPrecompute_VDup := Rule([VDup, [fPrecompute, @(1)], @(2)], e -> fPrecompute(VDup(@(1).val, @(2).val))),

    RCVScat_sv__fId := Rule(@(1,RCVScat_sv,e->IsInt(2*e.func.domain()/getV(e)) and ObjId(e.func) = fId),
        e->let(v:=getV(@(1).val), When(Cols(@(1).val)<>2*@(1).val.func.domain(),
            VGath_zero(Cols(@(1).val)/v, 2*@(1).val.func.domain()/v, v), VGath(fId(2*@(1).val.func.domain()/v), v))))
));

Class(RulesVRCTermDiag, RuleSet);
RewriteRules(RulesVRCTermDiag, rec(
    VRCLR_VDiag_x_I := Rule([@(1, VRCLR), @(2, VDiag_x_I)], e->VTensor(RC(Diag(@(2).val.element)), @(2).val.v))
));

Class(RulesVRCTerm, RulesVRCTermDiag);
RewriteRules(RulesVRCTerm, rec(
    VIxL := Rule(VIxL, (e, cx) -> e.implement(cx.opts.vector.isa)),
    VL := Rule(VL, (e, cx) -> e.implement(cx.opts.vector.isa)),
#--
### Rules for SAR -- to be moved and fixed...

    Term_VTensorInd_ScatGath := Rule([@(1, VTensorInd), @(2, ScatGath), @(3)],
        e->ScatGath(_fTensorInd(@(2).val.sfunc, @(3).val), _fTensorInd(@(2).val.gfunc, @(3).val))),

    RC_ScatGath := Rule([@@(1, RC), @(2, ScatGath)], (e,cx)->@(2).val.toloopRCVec(@(2).val.maxBkSize(), Last(cx.VContainer).isa.getV())),
##    VRCLR_ScatGath := Rule([@(1, VRCLR), @(2, ScatGath)], e->@(2).val.toloopRCVec(@(2).val.maxBkSize(), @(1).val.v)),

    RCVGathRCSVcat_sv_fTensor := Rule([@(1, [RCVGath_sv, RCVScat_sv]),
                            [@(2,fTensor), ..., [fId, @(3).cond(e -> Gcd(@(1).val.v/@(1).val.sv, EvalScalar(e)) = @(1).val.v)]]],
     e -> let(v := @(1).val.v, sv := @(1).val.sv,
          n := EvalScalar(@(3).val),   gcd := Gcd(v / sv, n),
          @(1).val.vecVariant(fTensor(DropLast(@(2).val.children(), 1), fId(2*n/gcd)), v))),

#    Id_RCV1 := Rule([@(1,[RCVScat_sv,RCVGath_sv]), @(2,fId,e->(IsValue(e.n) or IsInt(e.n)) and IsInt(EvalScalar(e.n/@(1).val.v)))], e->I(EvalScalar(@(2).val.n))),
#
#    Term__VScat_ScatGath := ARule(Compose, [@(1, VScat), @@(2, ScatGath, (e,cx)->not ForAny(_VRCFamily, i->IsBound(cx.(i.name)) and cx.(i.name) <> []))],
#        e->[@(1).val * @@(2).val.toloopVec(@@(2).val.maxBkSize(), @(1).val.v)]),
#
#    # in the precond of this rule there was a guard on VRCXX, but that was never matching due to an error. so I dont know if it was needed at all...
#    Term__ScatGath := Rule(@@(1, ScatGath, (e,cx)->not ForAny([VTensor, VTensorInd], i->IsBound(cx.(i.name)) and cx.(i.name) <> []) and
#        (IsBound(cx.VContainer) and cx.VContainer <> [])),
#        (e,cx)->@@(1).val.toloopVec(@@(1).val.maxBkSize(), Last(cx.VContainer).isa.getV())),
#
    Term_NeedInterleavedComplex_VRC := Rule([@(1, VRC), @(2, NeedInterleavedComplex)], e->RC(@(2).val.child(1))),
#    Term_NeedInterleavedComplex_RC := Rule([@(1, RC), @(2, NeedInterleavedComplex)], e->DRC(@(2).val.child(1))),
#
#    RC_GathScat_sv_fTensor := Rule([@(1, [VGath_sv, VScat_sv]), [@(2,fTensor), ..., [fId, 2]]],
#        e -> let(v := @(1).val.v, sv := @(1).val.sv,
#            @(1).val.rcVariant(fTensor(DropLast(@(2).val.children(), 1)), v, sv))),
#
    RC_XXX := Rule([@(1, RC), @(2, [VGath, VScat, VScatAcc])], e->ObjId(@(2).val)(fTensor(@(2).val.func, fId(2)), @(2).val.v)),
#    RC_XXX_sv := Rule([@(1, RC), @(2, [VGath_sv, VScat_sv])], e->@(2).val.rcVariant(@(2).val.func, @(2).val.v, @(2).val.sv)),
### end SAR
#--
#   these rules are buggy, as Scat_sv does zero padding.
#    RCVScat_sv_toVScat := Rule(@(1, RCVScat_sv, e->e.sv=1 and e.v=2), e -> VScat(e.func, e.v)),
#    RCVGath_sv_toVGath := Rule(@(1, RCVGath_sv, e->e.sv=1 and e.v=2), e -> VGath(e.func, e.v)),

#-- VPrm_x_I --

    #NOTE: Check if this rule is correct!
    #VRC_VPrm_x_I := Rule([@(1, VRC), @(2, VPrm_x_I)], e->let(p := @(2).val, v := p.v, b := p.dims()[1]/v,
    #    Compose(
    #        Tensor(I(2), VTensor(Prm(p.func), v)).sums().unroll()
    #    ))),

    # YSV: we use NoDiagPullin to prevent diagonals from going into the loops resulting from Tensor(I(2),...)
    #      because in some cases they can't be sucked in completely and one ends up with multiple Scat * Diag * Scat
    #      sequences inside a SUM, which overlap, implying that it is a SUMAcc. However, Spiral does not know
    #      that, and generates invalid code. I don't know of a better way to handle this at the moment.
    VRCLR_VPrm_x_I := Rule([@(1, [VRCLR, VRC]), @(2, VPrm_x_I)], e->let(p := @(2).val, v := p.v, b := p.dims()[1]/v,
        Compose(
            VTensor(Prm(L(2*b, b)), v),
            NoDiagPullin(Tensor(I(2), VTensor(Prm(p.func), v)).sums().unroll()),
            VTensor(Prm(L(2*b, 2)), v)
        ))),

    VRCL_VPrm_x_I := Rule([@(1, VRCL), @(2, VPrm_x_I)], e->let(p := @(2).val, v := p.v, b := p.dims()[1]/v,
        Compose(
            VIxL(b, 2, v),
            VTensor(Prm(L(2*b, b)), v),
            NoDiagPullin(Tensor(I(2), VTensor(Prm(p.func), v)).sums().unroll()),
            VTensor(Prm(L(2*b, 2)), v)
        ))),

    VRCR_VPrm_x_I := Rule([@(1, VRCR), @(2, VPrm_x_I)], e->let(p := @(2).val, v := p.v, b := p.dims()[1]/v,
        Compose(
            VTensor(Prm(L(2*b, b)), v),
            NoDiagPullin(Tensor(I(2), VTensor(Prm(p.func), v)).sums().unroll()),
            VTensor(Prm(L(2*b, 2)), v),
            VIxL(b, v, v)
        ))),

#-- VGath ------------------
    VRCL_VGath := Rule([@(1, VRCL), @(2, VGath)], e->let(v := @(2).val.v,
        Compose(
            VIxL(@(2).val.func.domain(), 2, v),
            VGath(fTensor(@(2).val.func, fId(2)), v)
        ))),

    VRCL_VScat := Rule([@(1, VRCL), @(2, [VScat, VScatAcc])], e->let(v := @(2).val.v,
        Compose(
            ObjId(@(2).val)(fTensor(@(2).val.func, fId(2)), v),
            VIxL(@(2).val.func.domain(), 2, v)
        ))),

    VRCL_VGath_sv := Rule([@(1, VRCL), @(2, VGath_sv)], e->let(v := @(2).val.v, sv := @(2).val.sv,
        Compose(
            VIxL(Rows(@(2).val)/v, 2, v),
            RCVGath_sv(@(2).val.func, v, sv, @(2).val.rem)
        ))),

    VRCL_IxVGath_pc := Rule([@(1, VRCL), @(2, IxVGath_pc)], e -> let(
	g := @(2).val, v := g.v,
        Compose(
            VIxL(g.k * _roundup(g.n, g.v) / v, 2, v),
            IxRCVGath_pc(g.k, g.N, g.n, g.ofs, v)
        ))),

    VRCLR_IxVGath_pc := Rule([@(1, VRCLR), @(2, IxVGath_pc, x->x.N*x.k mod x.v=0)], e -> let(
	g := @(2).val, v := g.v,
        Compose(
            VIxL(g.k * _roundup(g.n, v) / v, 2, v),
            IxRCVGath_pc(g.k, g.N, g.n, g.ofs, v),
            VIxL(g.k * g.N / v, v, v)
        ))),

    VRCL_VStretchGath := Rule([@(1, VRCL), @(2, VStretchGath)], e -> let(
	g := @(2).val,
	v := g.v,
	#XXX  Cond(???,
            Compose(
		VIxL(_roundup(Rows(g), v) / v, 2, v),
		RCVStretchGath(g.func, g.part, v))

	#XXX    VStretchGath(
	#XXX	fCompose(fTensor(fId(g.func.domain()/v), L(2*v, v)), fTensor(g.func, fId(2))),
	#XXX	...,
	#XXX	...)

        )),

    VRCR_VGath_zero:= Rule([@(1, VRCR),@(2, VGath_zero)], e -> let(g:=@(2).val,
        VGath_zero(2*g.N, 2*g.n, g.v) * VIxL(g.N, g.v, g.v))),

#-- VScat ------------------
    VRCR_VScat := Rule([@(1, VRCR), @(2, [VScat, VScatAcc])], e->let(v := @(2).val.v,
        Compose(
            ObjId(@(2).val)(fTensor(@(2).val.func, fId(2)), v),
            VIxL(@(2).val.func.domain(), v, v)
        ))),

    VRCR_VScat_sv := Rule([@(1, VRCR), @(2, VScat_sv)], e->let(v := @(2).val.v, sv := @(2).val.sv, # NOTE: acc variant missing
        Compose(
            RCVScat_sv(@(2).val.func, v, sv, @(2).val.rem),
            VIxL(Cols(@(2).val)/v, v, v)
        ))),

    VRCR_IxVScat_pc := Rule([@(1, VRCR), @(2, IxVScat_pc)], e->let(s:=@(2).val, v := s.v, # NOTE: acc variant missing
        Compose(
            IxRCVScat_pc(s.k, s.N, s.n, s.ofs, v),
            VIxL(s.k*_roundup(s.n, v)/v, v, v)
        ))),

    VRCLR_IxVScat_pc := Rule([@(1, VRCLR), @(2, IxVScat_pc, x->x.N*x.k mod x.v=0)], e -> let(
        s := @(2).val, v := s.v,
        Compose(
            VIxL(s.k * s.N / v, 2, v),
            IxRCVScat_pc(s.k, s.N, s.n, s.ofs, v),
            VIxL(s.k*_roundup(s.n, v)/v, v, v)
        ))),

    VRCR_VStretchScat := Rule([@(1, VRCR), @(2, VStretchScat)], e->let(s:=@(2).val, v := s.v,
        Compose(
            RCVStretchScat(s.func, s.part, v),
            VIxL(_roundup(Cols(s), v)/v, v, v)
        ))),

#----------------------------
    VRCLR_VPerm := Rule([@(1, VRCLR), @(2, VPerm)], e->let(p := @(2).val, v := p.vlen, b := p.dims()[1]/v,
        Compose(
            VTensor(Prm(L(2*b, b)), v),
            Tensor(I(2), p).sums().unroll(),
            VTensor(Prm(L(2*b, 2)), v)
        ))),

    VRCL_VPerm := Rule([@(1, VRCL), @(2, VPerm)], e->let(p := @(2).val, v := p.vlen, b := p.dims()[1]/v,
        Compose(
            VTensor(Prm(L(2*b, b)), v),
            Tensor(I(2), p).sums().unroll(),
            VTensor(Prm(L(2*b, 2)), v),
            VIxL(b, 2, v)
        ))),

    VRCR_VPerm := Rule([@(1, VRCR), @(2, VPerm)], e->let(p := @(2).val, v := p.vlen, b := p.dims()[1]/v,
        Compose(
            VIxL(b, v, v),
            VTensor(Prm(L(2*b, b)), v),
            Tensor(I(2), p).sums().unroll(),
            VTensor(Prm(L(2*b, 2)), v)
        ))),

#----------------------------
    VRCLR_Perm := Rule([@(1, VRCLR), @(2, Prm)], e->let(p := @(2).val, v := getV(@1.val), b := p.dims()[1]/v, n := @(2).val.dims()[2]/v,
        Compose(
            VIxL(n, 2, v),
            RCVScat_sv(fId(p.func.domain()), v, 1),
            RCVGath_sv(p.func, v, 1),
            VIxL(n, v, v)
        ))),

    VRC_Perm := Rule([@(1, VRC), @(2, Prm)], e->let(p := @(2).val, v := getV(@1.val), b := p.dims()[1]/v, n := @(2).val.dims()[2]/v,
            Compose (RCVScat_sv(fId(p.func.domain()), v, 1),
            RCVGath_sv(p.func, v, 1))
        )),

    VRCL_Split := Rule([@(1, VRCL), @(2, [VDiag, VTensor])], e->let(v := getV(@(2).val),
        Compose(
            VRCLR(@(2).val, v),
            VIxL(@(2).val.dims()[2]/v, 2, v)
        ))),

    VRCR_Split := Rule([@(1, VRCR), @(2, [VDiag, VTensor])], e->let(v := getV(@(2).val),
        Compose(
            VIxL(@(2).val.dims()[1]/v, v, v),
            VRCLR(@(2).val, v)
        ))),

    VRC_VTensor := Rule([@(1, VRC), @(2, [VTensor])], e->let(v := getV(@(2).val),
        Compose(
            VIxL(@(2).val.dims()[1]/v, v, v),
            VRCLR(@(2).val, v),
            VIxL(@(2).val.dims()[2]/v, 2, v)
        ))),

    VRC_VDiag := Rule([@(1, VRC), @(2, [VDiag])], e->let(
       v := getV(@(2).val), d := @(2).val.dims()[1],
       Cond(@(2).val.element.isReal(),
	   VDiag(diagTensor(@(2).val.element, fConst(TReal, 2, 1)), @(2).val.v),
	   VIxL(d/v, v, v) * VRCLR(@(2).val, v) * VIxL(d/v, 2, v)))),

    VRC_Blk := Rule([@(1, VRC), @(2, Blk)], e->RC(@(2).val)),

    VRCLR_VScat_zero := Rule([@(1, VRCLR), @(2, VScat_zero)], e->let(s:=@(2).val, VScat_zero(2*s.N, 2*s.n, s.v))),

    VRC_Gath := Rule([@(1, VRC), @(2, Gath)], e->let(g := @(2).val, v := getV(@1.val), b := g.dims()[1]/v, n := @(2).val.dims()[2]/v,
            Compose (VRCLR(VScat_sv(fId(g.func.domain()), v, 1), v),
            VRCLR(VGath_sv(g.func, v, 1), v))
        )),

    FormatPrm_Term := Rule([FormatPrm, @(1)], e -> Prm(@(1).val)),
));

Class(RulesSplitComplex, RuleSet);
RewriteRules(RulesSplitComplex, rec(
    VRCR_IxVScat_pc := ARule(Compose, [[@(1,[Prm, FormatPrm]), @(4,L,e->e.params[2]=2)], [@(2,VRCR), @(3,IxVScat_pc)]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val,
                    [IxVScat_pc(2*s.k, s.N, s.n, s.ofs, v), VPrm_x_I(L(2*c/v, 2), v)])),

    VRCL_IxVGath_pc := ARule(Compose, [[@(2,VRCL), @(3,IxVGath_pc)], [@(1,[Prm, FormatPrm]), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val,
                    [VPrm_x_I(L(2*r/v, r/v), v), IxVGath_pc(2*g.k, g.N, g.n, g.ofs, v)])),

    VRCR_VStretchScat := ARule(Compose, [[@(1,[FormatPrm,Prm]), @(4,L,e->e.params[2]=2)], [@(2,VRCR), @(3,VStretchScat)]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val,
                    [VStretchScat(fTensor(fId(2), s.func), 2*s.part, v), VPrm_x_I(L(2*c/v, 2), v)])),

    VRCL_VStretchGath := ARule(Compose, [[@(2,VRCL), @(3,VStretchGath)],[@(1,[FormatPrm,Prm]), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val,
                    [VPrm_x_I(L(2*r/v, r/v), v), VStretchGath(fTensor(fId(2), g.func), 2*g.part, v)])),

    VRCR_VScat_sv := ARule(Compose, [[@(1,[FormatPrm,Prm]), @(4,L,e->e.params[2]=2)], [@(2,VRCR), @(3,VScat_sv)]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val,
                    [VStretchScat(fTensor(fId(2), s.func), 2, v), VPrm_x_I(L(2*c/v, 2), v)])),

    VRCL_VGath_sv := ARule(Compose, [[@(2,VRCL), @(3,VGath_sv)],[@(1,[FormatPrm,Prm]), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val,
                    [VPrm_x_I(L(2*r/v, r/v), v), VStretchGath(fTensor(fId(2), g.func), 2, v)])),

    VRCR_VScat := ARule(Compose, [[@(1,[Prm, FormatPrm]), @(4,L,e->e.params[2]=2)], [@(2,VRCR), @(3,[VScat, VScatAcc])]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val,
                    [ObjId(@(3).val)(fTensor(fId(2), s.func), v), VPrm_x_I(L(2*c/v, 2), v)])),

    VRCL_VGath := ARule(Compose, [[@(2,VRCL), @(3,VGath)],[@(1,[Prm, FormatPrm]), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val,
                    [VPrm_x_I(L(2*r/v, r/v), v), VGath(fTensor(fId(2), g.func), v)])),

    VRCL_VScat := ARule(Compose, [[@(2,VRCL), @(3,[VScat, VScatAcc])],[@(1,Prm), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val,
                    [VPrm_x_I(L(2*r/v, r/v), v), ObjId(@(3).val)(fTensor(fId(2), g.func), v)])),

    VRCR_VTensor1 := ARule(Compose, [[@(1, [FormatPrm, Prm]), @(4,L,e->e.params[2]=2)], [@(2,VRCR), @(3,VTensor)]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val,
                    [VPrm_x_I(L(2*c/v, 2), v), VRCLR(s, v)])),

    VRCR_VTensor1a := ARule(Compose, [[@(1, [FormatPrm, Prm]), @(4,L,e->e.params[2]=2)], [@(2,VRC), @(3,VTensor)]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val,
                    [VPrm_x_I(L(2*c/v, 2), v), VRCL(s, v)])),

    VRCL_VTensor2 := ARule(Compose, [[@(2,VRCL), @(3,VTensor)],[@(1, [FormatPrm, Prm]), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, c:=Cols(@(3).val), g:=@(3).val,
                    [VRCLR(g,v), VPrm_x_I(L(2*c/v, c/v), v)])),

    VRCL_VTensor2a := ARule(Compose, [[@(2,VRC), @(3,VTensor)],[@(1, [FormatPrm, Prm]), @(4,L,e->e.params[2]=e.params[1]/2)]],
                e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val,
                    [VRCR(g,v), VPrm_x_I(L(2*r/v, r/v), v)])),

#    VRCLR_VGath_split := Rule([@(1, VRCLR), @(2, VGath_sv)],
#                e -> let(g := @(2).val, v:= g.v, func := g.func, n := func.domain(), N:= func.range(),
#                        VGath_sv(fCompose(fTensor(L(2*N/v,2), fId(v)), fTensor(fId(2), func), fTensor(L(2*n/v,n/v), fId(v))), v, g.sv))),
#
#    VRCLR_VScat_split := Rule([@(1, VRCLR), @(2, VScat_sv)],
#                e -> let(g := @(2).val, v:= g.v, func := g.func, n := func.domain(), N:= func.range(),
#                        VScat_sv(fCompose(fTensor(L(2*N/v,2), fId(v)), fTensor(fId(2), func), fTensor(L(2*n/v,n/v), fId(v))), v, g.sv)))
#
    VGath_FormatPrm := ARule(Compose, [[@(1, VGath), [fTensor, fBase, @(2, fId)]],
            [@(3, FormatPrm), [fTensor, fId, @(4, L, e->IsInt(@(2).val.domain()/e.domain()))]]],
        e->[FormatPrm(fTensor(fId(Rows(@(1).val)/@(4).val.domain()), @(4).val)), @(1).val]),

    FormatPrm_VScat := ARule(Compose, [[@(1, FormatPrm), [fTensor, fId, @(2, L)]],
            [@(3, [VScat, VScatAcc]), [fTensor, fBase, @(4, fId, e->IsInt(e.domain()/@(2).val.domain()))]]],
        e->[@(3).val, FormatPrm(fTensor(fId(Cols(@(3).val)/@(2).val.domain()), @(2).val))]),

    VRCL_VGath_fIdxL := ARule(Compose, [[@(2,VRCL), @(3,VGath)], [@(1,FormatPrm), [@(4,fTensor), fId, @(5, L, e->e.params[1]=2*e.params[2])]]],
        e -> let(v:=@(2).val.v, r:=Rows(@(3).val), g:=@(3).val, [VGath(fTensor(g.func, fId(2)), v)])),

    VRCR_VScat_fIdxL := ARule(Compose, [[@(1, FormatPrm), [@(4,fTensor), fId, @(5, L, e->e.params[2]=2)]], [@(2,VRCR), @(3,[VScat, VScatAcc])]],
        e -> let(v:=@(2).val.v, c:=Cols(@(3).val), s:=@(3).val, [ObjId(@(3).val)(fTensor(s.func, fId(2)), v)]))

));

Class(RulesVBlkInt, RuleSet);
RewriteRules(RulesVBlkInt, rec(
    Merge_RulesVBlkInt_VRC_rt := ARule(Compose, [@(1, VBlkInt), @(2, VRC)],
                e -> let(v1 := @(1).val, v2 := @(2).val, v := v1.v, [ VIxL(Rows(v1)/(2*v), v, v), v1.child(1), VRCL(v2.child(1), v) ])),

    Merge_RulesVBlkInt_VRC_lft := ARule(Compose, [@(2, VRC), @(1, VBlkInt)],
                e -> let(v1 := @(1).val, v2 := @(2).val, v := v1.v, [ VRCR(v2.child(1), v), v1.child(1), VIxL(Rows(v1)/(2*v), 2, v) ])),

    Merge_RulesVBlkInt_vRC_rt := ARule(Compose, [@(1, VBlkInt), @(2, vRC)],
                e -> let(v1 := @(1).val, v := v1.v, [ VIxL(Rows(v1)/(2*v), v, v), v1.child(1), VIxL(Cols(v1)/(2*v), 2, v), @(2).val ])),

    Merge_RulesVBlkInt_vRC_lft := ARule(Compose, [@(2, vRC), @(1, VBlkInt)],
                e -> let(v1 := @(1).val, v := v1.v, [ @(2).val, VIxL(Rows(v1)/(2*v), v, v), v1.child(1), VIxL(Rows(v1)/(2*v), 2, v) ])),
));


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

Class(cos, AutoFoldExp, rec(
  computeType := (self) >> TReal,

));

Class(sin, AutoFoldExp, rec(
  computeType := (self) >> TReal,
));

Class(tan, AutoFoldExp, rec(
  computeType := (self) >> TReal,
));

Class(TIVReal, AtomicTyp, rec(
    hash := (_val, size) -> let(val := When(IsList(_val), _val, [_val, _val]),
        h := DoubleRep64(val[1]) + DoubleRep64(val[2]),
        1 + (h mod size)),
    check := v -> Cond(IsDouble(v) or IsList(v) and Length(v) = 2 and v[1] <= v[2], v, Error("<v> must be a Double or an ordered list of length 2")),
    realType    := self >> self,
    print := self >> Print(self.__name__, "(", self.t, ")"),
    base_t := self >> self.t,
    zero := self >> self.value(0.0),
    one := self >> self.value(1.0),
    __call__ := (self, t) >>
        WithBases(self, rec(
        t    := Checked(IsType(t), t),
        operations := TypOps))
));

Class(TIVBool, AtomicTyp, rec(
    hash := (val, size) -> 1 + (InternalHash(val) mod size),
    check := v -> Cond(IsInt(v) and v in [0, 1, -1], v, Error("<v> must be in [-1, 0, 1]")),
    zero := self >> self.value(0),
    one := self >> self.value(1)
));

Class(ivenv, chain);

Class(RulesHCOLIVArithType, RuleSet, rec(inType := "iCode", outType := "iCode"));
RewriteRules(RulesHCOLIVArithType, rec(
	ivenv_prop_types_bools := ARule(ivenv, [[@(1, assign), @(2,var).cond(e->e.t<>TInt),
		@(3).cond(e-> e.t<>@(2).val.t and e.t=TBool)], @(4)],
        e->let(
		nvar := var.fresh_t("n", TInt),
		#Print("New Type 2: ", @(1).val,"(",@(2).val, ",",@(3).val,")\n", @(2).val.t, " <> ", @(3).val.t, "\n New Var Type: ", nvar, ",",nvar.t," \n"),
	  [decl([nvar], SubstVars(chain(assign(nvar, @(3).val), @(4).val), rec((@(2).val.id) := nvar )))] ) ),

    ivenv_prop_types := ARule(ivenv, [[@(1, assign), @(2,var),
		@(3).cond(e-> e.t<>@(2).val.t and not (e.t in [TVect(T_Real(64), 2), TIVReal, TBool]))], @(4,...)],
        e->let(
		nvar := var.fresh_t("n", @(3).val.t),
		#Print("New Type: ", @(1).val,"(",@(2).val, ",",@(3).val,")", @(2).val.t, " <> ", @(3).val.t, " ", nvar, "\n", @(4).val, "\n"),
	  [decl([nvar], SubstVars(chain(assign(nvar, @(3).val), @(4).val), rec((@(2).val.id) := nvar )))] ) ),

));

Class(RulesHCOLIVArith, RuleSet, rec(inType := "iCode", outType := "iCode"));
RewriteRules(RulesHCOLIVArith, rec(
    ivenv_chain := Rule(@(1, ivenv, e->ForAny(e.cmds, f->ObjId(f) = chain)),
        e->ApplyFunc(ivenv, Flat(List(@(1).val.cmds, j->When(ObjId(j)=chain, j.cmds, j))))),
    ivenv_xyz_decl := ARule(ivenv, [@(1), @(2, decl)],
        e->[decl(@(2).val.vars, chain(@(1).val, @(2).val.cmd))]),
    ivenv_decl := Rule(@(1, ivenv, e->ObjId(e.cmds[1])=decl),
        e -> decl(@(1).val.cmds[1].vars, ivenv(Concat([@(1).val.cmds[1].cmd], Drop(@(1).val.cmds, 1))))),
    ivenv_drop_selfassign := ARule(ivenv, [@(1, assign, e->IsVar(e.loc) and IsVar(e.exp) and e.exp=e.loc), @(2)],
        e->[@(2).val]),
    ivenv_creturn := Rule(@(1, ivenv, e->ObjId(Last(e.cmds))=creturn),
        e->chain(ivenv(DropLast(@(1).val.cmds, 1)), Last(@(1).val.cmds))),
    loop_pull_const_array_tcast := Rule([@(1, loop), @(2),  @(3), [@(4, chain), [@(5, assign), @(6,var), [@(7, tcast), @(8), @(9,nth, e->IsValue(e.idx))]],...]],
        e->let(#Print(e, "\n"),
		chain(@(5).val, loop(@(1).val.var, @(1).val.range, chain(Drop(@(4).val.cmds,1)))))),
    ivenv_state := Rule([@(1, tcast), @(2), @@(3, nth, (e, cx)->IsBound(cx.opts.state) and e.loc.id=cx.opts.state.id)],
        (e, cx)->nth(cx.opts.ivstate, e.args[2].idx) )
));

RulesCodeUnrollHACMIVArth := CopyFields(MergedRuleSet( RulesHCOLIVArith, RulesUnrollHCOL, RulesStrengthReduce,  RulesCodeHCOL),
    rec(inType:="iCode", outType := "iCode"));

RulesCodeHACMIVArth := CopyFields(MergedRuleSet(RulesStrengthReduce, RulesCodeHCOL, RulesHCOLIVArith),
    rec(inType:="iCode", outType := "iCode"));

RulesCodeUnrollHACMIVArthType := CopyFields(MergedRuleSet(RulesCodeUnrollHACMIVArth, RulesHCOLIVArithType),
	rec(inType :="iCode", outType:="iCode"));

Class(IVArithMixin, rec(
    precision := "double",
    TRealCtype := "double",
    IVRealType := TIVReal(T_Real(64)),
    IVBoolType := TInt,
    IVValueType := TIVReal(T_Real(64)),
    codeRuleSet := RulesCodeUnrollHACMIVArth
));

Class(ivExp, errExp);

Class(RealEPS, errExp);

Class(RealMPS, errExp);

Class(IVArith, SumsBase, BaseContainer, rec(
    rng := meth(self) return self._children[1].rng(); end,
    dmn := meth(self) return self._children[1].dmn(); end,
    toOperator := self >> (vec -> self.child(1).toOperator()(vec))
));

Class(TIVArith, Tagged_tSPL_Container, rec(
    abbrevs :=  [ s -> Checked(IsSPL(s), [s]) ],
    transpose := self >> CopyFields(self, rec(transposed := not self.transposed)),
    dims := self >> [self.params[1].dims()[1], self.params[1].dims()[2]],
    isReal := True,
    toOperator := self >> (vec -> let(r := self.params[1].toOperator()(vec), List(r, i->ivExp(i))))
));

NewRulesFor(TIVArith, rec(
    TIVArith_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [[nt.params[1]]],
        apply := (t, C, Nonterms) -> IVArith(C[1])
    )
));

HCOLSumsGen.IVArith := (self, o, opts) >> IVArith(self(o.child(1), opts));

Class(TypeUpdate, HierarchicalVisitor, rec(
	__call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        return res;
    end,
	func := (self, o, opts) >> let(func(o.ret, o.id, o.params, self(o.cmd, opts))),
	decl := (self, o, opts) >>  decl(o.vars, self(o.cmd, opts)),
	chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> self(i, opts))),
    loop := (self, o, opts) >>  loop(o.var, o.range, self(o.cmd, opts)),

	assign := (self, o, opts) >> assign(o.loc, self(o.exp, opts)),
	add := (self, o, opts) >> add(self(o.args[1], opts), self(o.args[2], opts)),
	sub := (self, o, opts) >> sub(self(o.args[1], opts), self(o.args[2], opts)),
	mul := (self, o, opts) >> mul(self(o.args[1], opts), self(o.args[2], opts)),
	div := (self, o, opts) >> div(self(o.args[1], opts), self(o.args[2], opts)),
	cond := (self, o, opts) >> cond(o.args[1], self(o.args[2], opts), self(o.args[3], opts)),
	abs := (self, o, opts) >> abs(self(o.args[1], opts)),
	logic_and := (self, o, opts) >> logic_and(self(o.args[1], opts), self(o.args[2], opts)),
	logic_or := (self, o, opts) >> logic_or(self(o.args[1], opts), self(o.args[2], opts)),
	gt  := (self, o, opts) >> gt(self(o.args[1], opts), self(o.args[2], opts)),
	lt  := (self, o, opts) >> lt(self(o.args[1], opts), self(o.args[2], opts)),
	geq  := (self, o, opts) >> geq(self(o.args[1], opts), self(o.args[2], opts)),
	leq  := (self, o, opts) >> leq(self(o.args[1], opts), self(o.args[2], opts)),
	min := (self, o, opts) >> min(self(o.args[1], opts), self(o.args[2], opts)),
	max := (self, o, opts) >> max(self(o.args[1], opts), self(o.args[2], opts)),

	nth := (self, o, opts) >> o,
	Value := (self, o, opts) >> o,
	var := (self, o, opts) >> o,

	skip := (self, o, opts) >> o,
	tcast := (self, o, opts) >> o,

	creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,

	eq := (self, o, opts) >> eq(self(o.args[1], opts), self(o.args[2], opts)),
));

HCOLCodegen.IVArith := (self, o, y, x, opts) >> let(
	#Generate non interval code
    cc := opts.codegen.OLCompose(o.child(1), y, x, opts),

	#Collect array of bools and make them into arrays of ints
	ba_vars := Filtered(FoldL(Collect(cc, var), (a,b)->When(not b.id in List(a, i->i.id), Concat([b],a), a), []),
    	    e->ObjId(e.t) = TArray and e.t.base_t()=TBool),
    ba_nvars := List(ba_vars, c-> [c, var.fresh_t("U", TArray(TInt, c.t.size))] ),
    ba_svars := FoldL(ba_nvars, (b, a)->CopyFields(rec((a[1].id):= a[2]), b), rec()),
    ba_cc1 := decl(List(ba_nvars, c->c[2]), SubstVars(cc, ba_svars)),

	#Cast bools variables into ints but make sure that they are not part of the input parameters
	b_vars :=Unique(Collect(ba_cc1, @(1,var).cond(e->e.t = TBool))),
	b_local_vars := Difference(b_vars, opts.params),
	b_global_vars := Difference(b_vars, b_local_vars),
	b_no_global := SubstBottomUp(ba_cc1, @(1,var).cond(e->e.t=TBool), f->let(Print(@(1).val.id, "\n"),
							tcast(TInt, @(1).val))),

	#Cast pointers to bools into pointers to ints
	b_cc2 := b_no_global,

	#Convert boolean constants into ints
	b_cc3 := SubstBottomUp(b_cc2, @(1,Value).cond(e->e.t=TBool),
        f->let(#Print(@(1).val, "\n"),
			Cond(@(1).val=V_true,  V(1),
			     @(1).val=V_false, V(0),
						   @(1).val))),

	#Collect array variables make them into arrays of intervals
    vars := Filtered(FoldL(Collect(b_cc3, var), (a,b)->When(not b.id in List(a, i->i.id), Concat([b],a), a), []),
    	    e->ObjId(e.t) = TArray and e.t.base_t() in [TReal, TDouble, T_Real(32), T_Real(64)]),
    nvars := List(vars, c-> [c, var.fresh_t("U", TArray(TIVReal(c.t.base_t()), c.t.size))] ),
    svars := FoldL(nvars, (b, a)->CopyFields(rec((a[1].id):= a[2]), b), rec()),
    cc1 := decl(List(nvars, c->c[2]), SubstVars( cc, svars)),

	#Cast doubles and reals variables into intervals
    cc11 := SubstBottomUp(cc1, @(1,var).cond(e->e.t in [TReal, TDouble, T_Real(32), T_Real(64)]), f->tcast(opts.IVRealType, @(1).val)),

	#Cast pointers to doubles and reals into intervals
    cc2 := SubstBottomUp(cc11, [@(1,nth), @(2, var, e->ObjId(e.t) = TPtr and e.t.base_t() in [TReal, TDouble, T_Real(32), T_Real(64)]), @(3)], f->tcast(opts.IVRealType, @(1).val)),

	#Cast constants into intervals
    cc3 := SubstBottomUp(cc2, @(1,Value).cond(e->e.t in [TReal, TDouble, T_Real(32), T_Real(64)]),
        f->let(tcast(opts.IVValueType, @(1).val))),

	result := ivenv(cc3),

	result2 := TypeUpdate(result, opts),

	result2
);

HCOLUnparser.RealEPS := (self,o,i,is) >> Print(Cond(o.args[1] = T_Real(64), "DBL_MIN", o.args[1] = T_Real(32), "FLT_MIN", "UNKNOWN_MIN"));
HCOLUnparser.RealMPS := (self,o,i,is) >> Print(Cond(o.args[1] = T_Real(64), "DBL_MAX", o.args[1] = T_Real(32), "FLT_MAX", "UNKNOWN_MAX"));
HCOLUnparser.ivenv := (self,o,i,is) >> self(chain(o.cmds), i, is);

HCOLUnparser.TIVReal := (self, t, vars, i, is) >>
		Print("interval_t ", self.infix(vars, ", ", i + is));
HCOLUnparser.TVect := (self, t, vars, i, is) >> Print("abc_interval_t ", self.infix(vars, ", ", i + is));

HCOLSSEUnparser.ivenv := (self,o,i,is) >> Print(Blanks(i), "{\n",
    Blanks(i+is), "unsigned _xm = _mm_getcsr();\n",
    Blanks(i+is), "_mm_setcsr(_xm & 0xffff0000 | 0x0000dfc0);\n",
    self(chain(o.cmds),i+is,is),
    Blanks(i+is),
    Cond(IsBound(self.opts.compiler) and self.opts.compiler = "IntelC", "__asm nop;\n",
        IsBound(self.opts.compiler) and self.opts.compiler = "GnuC", "asm volatile(\"\":::\"memory\");\n",
        "// BASIC BLOCK BARRIER\n"
    ),
    Blanks(i+is), "if (_mm_getcsr() & 0x0d) {\n",
    Blanks(i+2*is), "_mm_setcsr(_xm);\n",
	Blanks(i+2*is),
    Cond(self.opts.useCReturn, "return -1;\n", "Y[0] = -1;\n"),
    Blanks(i+is), "}\n",
    Blanks(i+is), "_mm_setcsr(_xm);\n",
    Blanks(i), "}\n");

HCOLSSEUnparser.RealEPS := HCOLUnparser.RealEPS;
HCOLSSEUnparser.RealMPS := HCOLUnparser.RealMPS;

Declare(ToIVArithBasic);

Class(ToIVArithBasic_Base, HierarchicalVisitor, rec(
    __call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        trace_log.addConversion(ObjId(arg[2]), arg[2], res, var);
       return res;
    end,
    func := (self, o, opts) >> let(Print("CONVERSION\n\n"),
		func(o.ret, o.id, FoldL(o.params, (a, b)->Concat(a, When(IsBound(opts.state) and b=opts.state, [opts.ivstate], [b])), []), self(o.cmd, opts))),
    creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,
    decl := (self, o, opts) >> let(
        ovars := Filtered(o.vars, e->ObjId(e.t) = TIVReal),
        svars := FoldR(ovars, (b,a)->CopyFields(rec((a.id):=var.fresh_t("u", TVect(T_Real(64), 2))), b), rec()),
        vars := Filtered(o.vars, e->ObjId(e.t) <> TIVReal)::List(Filtered(RecFields(svars), i->not i in SystemRecFields), i->svars.(i)),
        cmd := SubstVars(o.cmd, svars),
        decl(vars, self(cmd, opts))),
    chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> ToIVArithBasic(i, opts))),
    loop := (self, o, opts) >> loop(o.var, o.range, self(o.cmd, opts)),
    nth := (self, o, opts) >> o,

    Value := (self, o, opts) >> let(
        When(ObjId(o.t) = TIVReal,
          TVect(T_Real(64), 2).value([-o.v, o.v]),
          o)),

	skip := (self, o, opts) >> o,

	tcast := (self, o, opts) >> o,
	logic_and := (self, o, opts) >> o,
	logic_or := (self, o, opts) >> o,
	assign := (self, o, opts) >> o,
));

Class(ToIVArithBasic, HierarchicalVisitor, rec(
    __call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        trace_log.addConversion(ObjId(arg[2]), arg[2], res, var);
       return res;
    end,
    func := (self, o, opts) >> let(Print("CONVERSION\n\n"),
		func(o.ret, o.id, FoldL(o.params, (a, b)->Concat(a, When(IsBound(opts.state) and b=opts.state, [opts.ivstate], [b])), []), self(o.cmd, opts))),
    creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,
    decl := (self, o, opts) >> let(
        ovars := Filtered(o.vars, e->ObjId(e.t) = TIVReal),
        svars := FoldR(ovars, (b,a)->CopyFields(rec((a.id):=var.fresh_t("u", TVect(T_Real(64), 2))), b), rec()),
        vars := Filtered(o.vars, e->ObjId(e.t) <> TIVReal)::List(Filtered(RecFields(svars), i->not i in SystemRecFields), i->svars.(i)),
        cmd := SubstVars(o.cmd, svars),
        decl(vars, self(cmd, opts))),
    chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> self(i, opts))),
    loop := (self, o, opts) >> loop(o.var, o.range, self(o.cmd, opts)),
    nth := (self, o, opts) >> o,

    Value := (self, o, opts) >> let(
        When(ObjId(o.t) = TIVReal,
          TVect(T_Real(64), 2).value([-o.v, o.v]),
          o)),

	skip := (self, o, opts) >> o,

	tcast := (self, o, opts) >> o,

	logic_and := (self, o, opts) >> cond(eq(o.args[1],o.args[2]), o.args[1], mul(o.args[1], o.args[2])),
	logic_or := (self, o, opts) >> cond(logic_or(eq(o.args[1], 1), eq(o.args[2],1)), 1, min(o.args[1], o.args[2])),

    assign := (self, o, opts) >> let(
	  Cond(
	    ObjId(o.exp) = var,
			let(
				chain(
					assign(o.loc[1], tcast(o.loc[1].t,o.exp)),
					assign(o.loc[2], tcast(o.loc[2].t,o.exp))
				)
			),
	    ObjId(o.exp) = tcast and (o.exp.args[1] = TIVReal or o.exp.args[1] = TVect(T_Real(64), 2)),
				Cond(
					o.exp.args[2].t = T_Real(32),
						let(
							x0 := var.fresh_t("x", T_Real(64)),
							x1 := var.fresh_t("x", T_Real(64)),
							decl([x0, x1], chain(
								self(assign([x0,x1], o.exp.args[2]), opts),
								assign(o.loc[1], x0+RealEPS(T_Real(32))),
								assign(o.loc[2], x1-RealEPS(T_Real(32)))
							))
						),
					IsDouble(o.exp.args[2]),
						let(
							x0 := var.fresh_t("x", T_Real(64)),
							x1 := var.fresh_t("x", T_Real(64)),
							decl([x0, x1], chain(
								self(assign([x0,x1], o.exp.args[2]), opts),
								assign(o.loc[1], x0),
								assign(o.loc[2], -x1)
							))
						),
					Error("Don't know how to convert <o.args[2]> to an interval.")),
		ObjId(o.exp) = lt and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(
				x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
                decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc, tcast(TInt, cond(lt(x10, x21), V(1), cond(lt(x20,x11), V(0),V(-1)))))
                ))
            ),
		ObjId(o.exp) = gt and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
                decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc, tcast(TInt, cond(gt(x11, x20), V(1), cond(gt(x21, x10), V(0), V(-1)))))
                ))
            ),

		ObjId(o.exp) = add  and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
			let(x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
				decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc[1], add(x11, x21)),
					assign(o.loc[2], add(x10, x20))
				))
		  ),
		ObjId(o.exp) = sub  and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
			let(x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
				decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc[1], sub(x11, x20)),
					assign(o.loc[2], sub(x10, x21))
				))
			),
		ObjId(o.exp) = abs  and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(
				x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x2 := var.fresh_t("x", T_Real(64)),
				x3 := var.fresh_t("x", T_Real(64)),
                decl([x10, x11, x2, x3], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					assign(x2, abs(x10)),
					assign(x3, abs(x11)),
					assign(o.loc[1], max(x2, x3)),
					assign(o.loc[2], min(x2, x3))
                ))
            ),
		assign(o.loc, self(o.exp, opts))
    )),
));

Class(ToIVArithSSE, HierarchicalVisitor, rec(
    __call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        trace_log.addConversion(ObjId(arg[2]), arg[2], res, var);
       return res;
    end,
    func := (self, o, opts) >> let(#Print("CONVERSION\n\n"),
		func(o.ret, o.id, FoldL(o.params, (a, b)->Concat(a, When(IsBound(opts.state) and b=opts.state, [opts.ivstate], [b])), []), self(o.cmd, opts))),
    creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,
    decl := (self, o, opts) >> let(
        ovars := Filtered(o.vars, e->ObjId(e.t) = TIVReal),
        svars := FoldR(ovars, (b,a)->CopyFields(rec((a.id):=var.fresh_t("u", TVect(T_Real(64), 2))), b), rec()),
        vars := Filtered(o.vars, e->ObjId(e.t) <> TIVReal)::List(Filtered(RecFields(svars), i->not i in SystemRecFields), i->svars.(i)),
        cmd := SubstVars(o.cmd, svars),
        decl(vars, self(cmd, opts))),
    chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> self(i, opts))),
    loop := (self, o, opts) >> loop(o.var, o.range, self(o.cmd, opts)),
    nth := (self, o, opts) >> o,
    Value := (self, o, opts) >> let(
        When(ObjId(o.t) = TIVReal,
          TVect(T_Real(64), 2).value([-o.v, o.v]),
          o)),
    var := (self, o, opts) >> o,
	skip := (self, o, opts) >> o,
    tcast := (self, o, opts) >> Cond(
        IsDouble(o.args[2]), vpack(-o.args[2], o.args[2]),
	IsValue(o.args[2]), TVect(T_Real(64), 2).value([-o.args[2].v, o.args[2].v]),
        o.args[2].t = T_Real(32), addsub_2x64f(vcvt_64f32f(vdup(RealEPS(T_Real(32)), 4)), vcvt_64f32f(vdup(o.args[2], 4))),
        o.args[2].t = T_Real(64), addsub_2x64f(vdup(RealEPS(T_Real(64))+RealEPS(T_Real(64)), 2), vdup(o.args[2], 2)),
	o.args[2].t = TIVReal(T_Real(64)),  TVect(T_Real(64),2).value(o.args[2]),
        Error("Don't know how to convert <o.args[2]> to an interval.")),

	logic_and := (self, o, opts) >> cond(eq(o.args[1],o.args[2]), o.args[1], mul(o.args[1], o.args[2])),
	logic_or := (self, o, opts) >> cond(logic_or(eq(o.args[1], 1), eq(o.args[2],1)), 1, min(o.args[1], o.args[2])),

    testc_4x32i:= (self, o, opts) >> o,

    assign := (self, o, opts) >> Cond(
	    ObjId(o.exp) = add and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
		  let(
			x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
			decl([x1, x2], chain(
				comment("addition"),
				self(assign(x1, o.exp.args[1]), opts),
				self(assign(x2, o.exp.args[2]), opts),
				assign(o.loc, add(x1, x2)) ) )
		  ),
		ObjId(o.exp) = sub and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
		  let(
			x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
			decl([x1, x2], chain(
				comment("sub"),
				self(assign(x1, o.exp.args[1]), opts),
				self(assign(x2, o.exp.args[2]), opts),
				assign(o.loc, add(x1, vushuffle_2x64f(x2, vparam([2,1])))) ) )
		  ),
        ObjId(o.exp) = mul and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(u := var.fresh_t("x", TVect(T_Real(64), 2)), a := var.fresh_t("x", TVect(T_Real(64), 2)),
                b := var.fresh_t("x", TVect(T_Real(64), 2)), c := var.fresh_t("x", TVect(T_Real(64), 2)),
                x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([u, a, b, c, x1, x2], chain(
					comment("mul"),
					self(assign(x1, o.exp.args[1]), opts),
					self(assign(x2, o.exp.args[2]), opts),
                    assign(u, addsub_2x64f(TVect(T_Real(64), 2).zero(), x1)),
                    assign(a, mul(u, x2)),
                    assign(b, mul(vushuffle_2x64f(u, vparam([2,1])), x2)),
                    assign(c, neg(min(a, b))),
                    assign(o.loc, add(max(max(a, b), vushuffle_2x64f(c, vparam([2,1]))), vdup(RealEPS(T_Real(64)), 2)))
                ))
            ),
        ObjId(o.exp) = abs and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(u := var.fresh_t("x", TVect(T_Real(64), 2)), x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([u, x1], chain(
					comment("abs"),
                    self(assign(x1, o.exp.args[1]), opts),
                    assign(u, vushuffle_2x64f(x1, vparam([2,1]))),
                    assign(o.loc, vshuffle_2x64f(min(x1, u), max(x1, u), vparam([1,2])))
                ))
            ),
		ObjId(o.exp) = pow and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and IsValue(o.exp.args[2]) and o.exp.args[2].v = 2,
			let( x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					self(assign(x1, mul(o.exp.args[1],o.exp.args[1])), opts),
					assign(o.loc, x1)
				))
			),
		ObjId(o.exp) = pow and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and IsValue(o.exp.args[2]) and IsInt(o.exp.args[2].v) and o.exp.args[2] > 2,
			let( x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					self(assign(x1, mul(o.exp.args[1], pow(o.exp.args[1],o.exp.args[2].v-1))), opts),
					assign(o.loc, x1)
				))
			),

		ObjId(o.exp) = div and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and not IsValue(o.exp.args[1]),
			let(
			Print("DIVISION - PART 1\n"),
			  x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
			  decl([x1], chain(
				self(assign(x1, mul(o.exp.args[1], 1.0/o.exp.args[2])), opts),
				assign(o.loc, x1)
			  ))
			),

		ObjId(o.exp) = div and <#o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and #>IsValue(o.exp.args[1]),
			let(
				x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					assign(x1, vdiv_2x64f(TVect(T_Real(64), 2).value([-o.exp.args[1].v,o.exp.args[1].v]), o.exp.args[2])),
					assign(o.loc, vshuffle_2x64f(x1, x1, vparam([2,1])))
				))
			),
		ObjId(o.exp) = neg and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
		    let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)),x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					self(assign(x1, TVect(T_Real(64), 2).value([0,0]), opts)),
					self(assign(x2, sub(x1, o.exp.args[1])), opts),
					assign(o.loc, x2)
				))
			),
        ObjId(o.exp) = max and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2], chain(
                    self(assign(x1, o.exp.args[1]), opts),
                    self(assign(x2, o.exp.args[2]), opts),
                    assign(o.loc, vshuffle_2x64f(min(x1, x2), max(x1, x2), vparam([1, 2])))
                ))
            ),
         ObjId(o.exp) = min and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2], chain(
                    self(assign(x1, o.exp.args[1]), opts),
                    self(assign(x2, o.exp.args[2]), opts),
                    assign(o.loc, vshuffle_2x64f(max(x1, x2), min(x1, x2), vparam([1, 2])))
                ))
            ),
        ObjId(o.exp) = geq <#and (o.loc.t = TBool or o.loc.t = TInt)#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
					comment("geq"),
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmpge_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
        ObjId(o.exp) = leq <#and o.loc.t = TBool#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmple_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
		ObjId(o.exp) = gt <#and (o.loc.t = TBool or o.loc.t = TInt)#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmpgt_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
        ObjId(o.exp) = lt <#and o.loc.t = TBool#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmplt_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
		ObjId(o.exp) = eq,
			let(Error("Eq not implemented for interval arithmetic.")),
		ObjId(o.exp) = neq,
			let(Error("Neq not implemented for interval arithmetic.")),

        assign(o.loc, self(o.exp, opts))
    ),
    mul := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    abs := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    max := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	add := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	sub := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    geq := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    leq := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	gt  := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    lt  := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	pow := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	div := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
));

HCOLSSEUnparser.TIVReal := (self, t, vars, i, is) >> Print("__m128d ", self.infix(vars, ", ", i + is));

CoSynthesizeStrategies.IVArithSSE := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOLv2a, RulesTerminateReductionHCOL, RulesSumsHCOLv2b], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArth, opts),
        (c, opts) -> HCOLProof_CodeConversion(c, ToIVArithSSE, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArthType, opts) ];

CoSynthesizeStrategies.IVArithBasic := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOLv2a, RulesTerminateReductionHCOL, RulesSumsHCOLv2b], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArth, opts),
        (c, opts) -> HCOLProof_CodeConversion(c, ToIVArithBasic_Base, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArth, opts) ];

HCOLSSEUnparser.addsub_2x64f := (self, o, i, is) >> Checked(Length(o.args) = 2,
        CondPat(o,
           [addsub_2x64f, @TReal, @TVect], self(addsub_2x64f(vdup(o.args[1],o.t.size), o.args[2]), i, is),
           [addsub_2x64f, @TVect, @TReal], self(addsub_2x64f(o.args[1], vdup(o.args[2], o.t.size)), i, is),
           [addsub_2x64f, @TInt,  @TVect], self(addsub_2x64f(vdup(_toReal(o.args[1]), o.t.size), o.args[2]), i, is),
           [addsub_2x64f, @TVect, @TInt],  self(addsub_2x64f(o.args[1], vdup(_toReal(o.args[2]), o.t.size)), i, is),
           [addsub_2x64f, @TVect, @TVect], self.printf("_mm_addsub_pd($1, $2)", o.args),
           [addsub_2x64f, @TVect, @(1).cond(e->e.t=TIVReal)], self.printf("_mm_addsub_pd($1, $2)", o.args),
           [addsub_2x64f, @(1).cond(e->e.t=TIVReal), @TVect], self.printf("_mm_addsub_pd($1, $2)", o.args),
           Error("Don't know how to unparse <o>. Unrecognized type combination")
    ));

HCOLSSEUnparser.max := (self, o, i, is) >> let(n := Length(o.args), When(
	IsVecT(o.t) and n >2, self.printf("_mm_max_$1($2, $3)", [self.ctype_suffix(o.t, _isa(self)), o.args[1],
		ApplyFunc(max, Drop(o.args, 1))]),
	CondPat(o,
		[max, @TVect, @TVect], self.prefix("_mm_max_" :: self.ctype_suffix(o.t, _isa(self)),o.args),
	    [max, @TVect, @(1).cond(e->e.t=TIVReal)], self.printf("_mm_max_pd($1, $2)", o.args),
        [max, @(1).cond(e->e.t=TIVReal), @TVect], self.printf("_mm_max_pd($1, $2)", o.args),
		Inherited(o, i, is))
));

HCOLSSEUnparser.min := (self, o, i, is) >> let(
	CondPat(o,
		[min, @TVect, @TVect], self.prefix("_mm_min_" :: self.ctype_suffix(o.t, _isa(self)),o.args),
	    [min, @TVect, @(1).cond(e->e.t=TIVReal)], self.printf("_mm_min_pd($1, $2)", o.args),
        [min, @(1).cond(e->e.t=TIVReal), @TVect], self.printf("_mm_min_pd($1, $2)", o.args),
		Inherited(o, i, is))
);

geq.computeType := self >> TBool;
leq.computeType := self >> TBool;
eq.computeType := self >> TBool;
pow.computeType := (self) >> self.args[1].t;
neq.computeType := (self) >> TBool;

min.computeType := self >> let(
		types := List(self.args, e->e.t),
		When(Length(Collect(types, TIVReal))>0,
			TIVReal(UnifyTypes(List(Collect(types, TIVReal), i->i.t))),   <# TVect(T_Real(64),2),  #>
			UnifyTypes(List(self.args, x->x.t)))
	);
max.computeType := self >> let(
		types := List(self.args, e->e.t),
		When(Length(Collect(types, TIVReal))>0,
			TIVReal(UnifyTypes(List(Collect(types, TIVReal), i->i.t))),   <# TVect(T_Real(64),2),  #>
			UnifyTypes(List(self.args, x->x.t)))
	);

VecExp.computeType := self >> let(
        t       := self.args[1].t,
        deref_t := When(IsPtrT(t), t.t, t),
        el_t    := Cond(IsVecT(deref_t), deref_t.t, deref_t),
	test    := Cond(el_t = TIVReal, el_t.t, el_t),
        TVect(test, self.v));

add.computeType := meth(self)
	    local len, t, ptr_args, other_args, sum;
	    len := Length(self.args);
	    if   len=0  then return TInt;
	    elif len=1  then return self.args[1].t;
	    else
            [ptr_args, other_args] := SplitBy(self.args, x->IsPtrT(x.t) or IsArrayT(x.t));
            if Length(ptr_args)=0 then
	       if (self.args[1].t = TIVReal or self.args[2].t = TIVReal) then
	         return TIVReal(T_Real(64));
	       else
	         return UnifyTypesL(self.args);
               fi;
            elif Length(ptr_args)=1 then
                sum := Sum(other_args);
                if other_args<>[] and not IsIntT(sum.t) then Error("Can't add non-integer to a pointer"); fi;
		   return self._ptrPlusOfs(ptr_args[1].t, sum);
            elif Length(other_args)=0 then
                return self._addPtrT(ptr_args);
            else
                return Error("Addition of more than one pointer and integers is not defined");
            fi;
	    fi;
    end;

sub.computeType := meth(self)
	    local len, t, ptr_args, other_args, sum;
	    len := Length(self.args);
	    if   len=0  then return TInt;
	    elif len=1  then return self.args[1].t;
	    else
            [ptr_args, other_args] := SplitBy(self.args, x->IsPtrT(x.t) or IsArrayT(x.t));
            if Length(ptr_args)=0 then
	       if (self.args[1].t = TIVReal or self.args[2].t = TIVReal) then
	         return TIVReal(T_Real(64));
	       else
	         return UnifyTypesL(self.args);
               fi;
            elif Length(ptr_args)=1 then
                sum := Sum(other_args);
                if other_args<>[] and not IsIntT(sum.t) then Error("Can't add non-integer to a pointer"); fi;
		   return self._ptrPlusOfs(ptr_args[1].t, sum);
            elif Length(other_args)=0 then
                return self._addPtrT(ptr_args);
            else
                return Error("Addition of more than one pointer and integers is not defined");
            fi;
	    fi;
    end;

mul.computeType := meth(self)
        local len, t, ptr_t, ptr_args, other_args, prod, args;
	args := self.args;

	len := Length(args);
	if   len=0  then return TInt;
	elif len=1  then return args[1].t;
	else
	    [ptr_args, other_args] := SplitBy(args, x->IsPtrT(x.t));
	    if Length(ptr_args)=0 then
	        if (self.args[1].t = TIVReal or self.args[2].t = TIVReal) then
  		  return TIVReal(T_Real(64));
		else
		  return UnifyTypesL(args);
		fi;
	    elif Length(ptr_args) > 1 then Error("Can't multiply pointers");
	    else
		prod := Product(other_args);
		if other_args<>[] and not IsIntT(prod.t) then Error("Can't multiply a pointer by a non-integer"); fi;
		return  self._ptrMul(ptr_args[1].t, prod);
	    fi;
	fi;
    end;

div.computeType := self >> When(self.args[1].t = TIVReal or self.args[2].t = TIVReal, TIVReal(T_Real(64)), UnifyTypes(List(self.args, x->x.t)));

TempArrayType := (child, y, x, index) ->
let(When(IsBound(child.a.t_in), child.a.t_in[index],
		let( X := Flat([x])[1], Y:= Flat([y])[1],
			Cond(
				IsBound(X.t.t) and X.t.t = TComplex, 		TComplex,
				IsBound(Y.t.t) and Y.t.t = TComplex, 		TComplex,
				ObjId(child) = ISumReduction, 				When(IsRec(child.idval), child.idval.t, TReal),
				ObjId(child) = PointWise, 					child.op.expr.t,
				X.t.t))));
TempArray := (y, x, child) -> let(cols := Flat([ Cols(child) ]),
	newType := TempArrayType(child, y, x, 1),
      StripList(
        List([ 1 .. Length(cols) ], (i) -> TempVec(TArray(TempArrayType(child,
               y, x, i), cols[i])))) );