JuMP abs in expression macro

I am hoping to extend the expression/objective JuMP macro to automatically introduce the necessary slack variable when using absolute values. I don’t have a lot of experience with JuMP internals and was hoping for some pointers. Essentially, if have two vectors x and y and I would like the to extend the macro so that

@expression m sum(abs.(x .- y))

becomes

@constraint m z .>= (x .- y)
@constraint m z .>= -(x .- y)
@expression m sum(z)

Hi,

One big issue with that is that does not always work.

A simple case is:

model = Model()
@variable(model, -1 <= x <= 1)
@objective(model, Max, abs(x))

your z would go to infinity in this case.

You might want to consider converting:
@constraint(model, λ >= norm(x,1))
into its current JuMP form
@constraint(model, [λ, x] in MOI.NormOneCone(1 + length(x)))

Note that λ can be a scalar expression and x a vector expression.

The absolute value case requires x to be a scalar expression.
@constraint(model, λ >= abs(x))
would become the valid JuMP constraint:
@constraint(model, [λ, x] in MOI.NormOneCone(2))

3 Likes

That is certainly an excellent point. I was more trying to create a small solution I could use personally understanding the limitations than suggesting it be used widespread where everyone expects it to be correct in every instance.

I apologize for this next question. It might be better placed in the first steps area as it is more about how to write macros as I am not very good at it.

So my current attempt is to write the macro

macro sumAbs(m, ex, name)
    slackVar = Symbol("slackVar", randstring(12))
    return quote
        @variable $m $slackVar[1:$(length(ex))]
        @constraint $m $slackVar .>= $ex
        @constraint $m $slackVar .>= -$ex

        $name = @expression $m sum($slackVar)
    end         
end

It’s not quite working just yet. Surprisingly to me, it does work if you make slackVar a scalar and not an array (so just delete [1:$(length(ex))]). But I can’t figure out how to define it as an array in the macro.

Any help?

There’s no need to write a macro for this.

function sumAbs(model, expr::Vector)
    slack = @variable(model, [1:length(expr)], lower_bound = 0.0)
    @constraint(model, slack .>= expr)
    @constraint(model, slack .>= -expr)
    return @expression(model, sum(slack))
end

model = Model()
@variable(model, x[1:3])
@variable(model, y[1:3])
ex = @expression(model, [a = 1:3], x[a] - y[a])
z = sumAbs(model, ex)

In general, you should almost never need to write a macro. They are difficult to write, and functions have the benefit of having typed arguments.

4 Likes