 # 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.

``````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