Adding constraints to jump model from dict

68 views Asked by At

Suppose I have a Dict where I store a config with the optimisation I want to do. So something like the below:

portfolio = Model(Ipopt.Optimizer)
@variable(portfolio, x[1:5])

max_ = 10.0
min_ = [1,2,3,4,5]
constraints = Dict{String, String}()
constraints["max_constraint"] = "x .<= max_"
constraints["min_constraint"] = "x  >= min_"

I want to loop through everything in constraints and put it into model as a constraint with the name as per the name of the key in the dict. So basically I want to do:

for k in keys(constraints)
    @constraint(portfolio, k, constraints[k])
end

This doesnt work for either constraint but I can copy paste the contents of the Dict to make it work:

@constraint(portfolio, max_constraint, x .<= max_)
@constraint(portfolio, min_constraint, x  >= min_)

So how can I add the constraints using the strings directly from the Dict? In addition is there a way to do this that is robust to if max_ and min_ fall out of scope (for instance the additional of constraints are added in a function call with the model being returned).

2

There are 2 answers

4
Dan Getz On BEST ANSWER

In order to get from strings describing the constraint to expressions, there needs to be a parsing stage. After parsing, the macro/function to add the constraint expression needs to be called. So the following did work for me in a test:

for (name, con) in constraints
    e = Meta.parse("@constraint(portfolio, $name, $con)")
    @eval $e
end

In any case, JuMP allows to produce constraints programmatically, and that may be something to explore.

Finally, if the strings describing the constraints are coming from an external file or source, using @eval introduces risks (security and such) and you may want to control the strings parsed and evaluated somehow.

0
Oscar Dowson On

Do not use @eval like suggested in other answers. It is unsafe, and it evaluates in the global scope, so you may get incorrect answers if you put your code in functions. For example:

julia> x = 1
1

julia> function foo(y)
           x = 2
           expr = Meta.parse(y)
           @eval $expr
       end
foo (generic function with 1 method)

julia> foo("x + 1")
2

You should instead store data in the config file.

For example, you might choose to store the configuration in a JSON file:

using JuMP, JSON
data = """{"upper_bound": 10, "lower_bound": [1, 2, 3, 4, 5]}"""
config = JSON.parse(data)
model = Model()
@variable(model, x[1:5])
set_lower_bound.(x, config["lower_bound"])
set_upper_bound.(x, config["upper_bound"])

The JuMP tutorial "Design patterns for larger models" is a good example of how to build and structure larger projects with configuration files: