Range constraint

I have a constraint that can be either a range or an equality depending upon whether a user passes a range (i.e. two arguments) or a single value as a constraint. I convert the range constraint to an equality using the standard technique, by defining a slack variable u. I define an abstract type SPECIAL_CONS with two subtypes Range and Equality.
What’s the correct way of generating this constraint and adding it to the model (we don’t know in advance whether a a single value or two values will be passed so that correct constraint is generated)?

using JuMP, Cbc

abstract type SPECIAL_CONS end
struct Range <: SPECIAL_CONS end
struct Equality <: SPECIAL_CONS end

function constraint_gen(lb,ub,::Range)
    @variable(model,0<=u<=ub-lb)
    return @constraint(model,u+sum(C[i]*x[i] for i in 1:10) == ub)
end
function constraint_gen(ub,::Equality)
    return @constraint(model,sum(C[i]*x[i] for i in 1:10) == ub)
end


function solve_model(constraint_gen::Function)
    c = rand(10)
    d = rand(10)
    model = Model(Cbc.Optimizer)
    @variable(model, x[1:10] >= 0)
    @constraint(model,sum(x[i]*c[i] for i=1:10) >=sum(d))
    constraint_gen(constraint_gen::Function)
    optimize!(model)
end

If I run the code using the following, it errors as model is not defined, but adding model as argument to the constraint_gen function also errors.

solve_model(constraint_gen(1,Equality()))

That sounds like it should work, what code did you run and what error did you get?

1 Like
abstract type SPECIAL_CONS end
struct Range <: SPECIAL_CONS end
struct Equality <: SPECIAL_CONS end

function constraint_gen(model,lb,ub,::Range)
    @variable(model,0<=u<=ub-lb)
    return @constraint(model,u+sum(C[i]*x[i] for i in 1:10) == ub)
end
function constraint_gen(model,ub,::Equality)
    return @constraint(model,sum(C[i]*x[i] for i in 1:10) == ub)
end

function solve_model(constraint_gen::Function)
    c = rand(10)
    d = rand(10)
    model = Model(Cbc.Optimizer)
    @variable(model, x[1:10] >= 0)
    @constraint(model,sum(x[i]*c[i] for i=1:10) >=sum(d))
    constraint_gen(constraint_gen::Function)
    optimize!(model)
end

solve_model(constraint_gen(model,1,Equality()))

This gives error “UndefVarError: model not defined”. I understand why I get this error, but how to correct it as model is generated by the function solve_model()?

Ah ok, you just need to pass the model along when you call constraint_gen. So change the line

to

constraint_gen(model, constraint_gen)
1 Like

Changing to

gives the same error. Calling solve_model(constraint_gen(model,1,Equality())) expects model to be an argument, and I am generating the model inside the function solve_model.

It is not obvious how the code will work to use the correct function definition depending upon if one value is passed solve_model(ub) or a range is passed solve_model(lb,ub)

Do I need to use something like below?

if one_input  
    solve_model(constraint_gen(model,1,Equality()))
elseif range
    solve_model(constraint_gen(model,0.5,1,Range()))
end

Looking closer, it seems you are also not passing the variables C and x, and are passing constraint_gen to itself! You need to put all the variables you need to use in function signature and then pass those variables when you call the function. So if you define the function constraint_gen(lb,ub,::Range) then you only have access to those variables inside the function (except if they are global variables, which it’s best to avoid), and you need to pass them all when you call the function.

Maybe the docs on functions might be helpful?

1 Like

Yes, I realised that I need to pass C and x as well so the function is defined like constraint_gen(model,x,C,lb,ub,::Range). This will still not address what my question is about. When I call the solve_model() it will then expect all these arguments. And how can I code so that it calls the right definition as shown in this pseudocode below?

if one_input  
    solve_model(constraint_gen(model,1,Equality()))
elseif range
    solve_model(constraint_gen(model,0.5,1,Range()))
end

You can use splatting, e.g. if args = (1,) (a one-element tuple containing the integer 1), then f(args...) calls the function f with one argument, the integer 1, whereas if args = (0.5, 1) then f(args...) calls f with two arguments, 0.5 and 1. So you use f(model, args..., Range()) or something like that. But I think I still don’t really understand the broader context of your code structure.

The code structure is weird because I don’t know how to implement this constraint depending upon what inputs are passed; hence the question.

Structure your code like this

using JuMP, Cbc

function constraint_gen(model, c, x, bound::Tuple{Float64,Float64})
    lb, ub = bound
    @variable(model, 0 <= u <= ub - lb)
    @constraint(model, u + sum(c[i] * x[i] for i in 1:10) == ub)
    return
end

function constraint_gen(model, c, x, bound::Float64)
    @constraint(model, sum(c[i] * x[i] for i in 1:10) == bound)
    return
end

function solve_model(bound)
    c = rand(10)
    d = rand(10)
    model = Model(Cbc.Optimizer)
    @variable(model, x[1:10] >= 0)
    @constraint(model,sum(x[i] * c[i] for i in 1:10) >= sum(d))
    constraint_gen(model, c, x, bound)
    optimize!(model)
end

solve_model(1.0)
solve_model((1.0, 2.0))
5 Likes

Thank you

1 Like