Run time constraints in optimisation

In an optimisation problem, I have some variables which must take same values. A simple example is given below where the optimisation variables with a unique identifier should have same values:

using DataFrames
using JuMP
using Clp

function generate_data()
df = DataFrame("SN" => (1:10),"Unique_ID"=> ["A","B","A","B","C","D","E","F","G","A"],
                       "Sector" => ["Priv", "Gov", "Utl", "Priv","Priv","Gov","Utl","Priv","Priv","Gov"],
                       "Values" => vec(float(rand(20:140,10,1))))
end

function build_model()
model = Model(Clp.Optimizer)
@variable(model,x[1:length(c)]>=0)
@objective(model,Min,sum(x[i]*c[i] for i = 1:length(c)))
@constraint(model,con1,sum(x[i] for i = 1:length(c))==1)
@constraint(model,con2,sum(x[i]*c[i] for i = 1:length(c))>=100)
return model,x
end

function run_model()
generate_data()
c = df.Values
build_model()
optimize!(model)
println("Results",JuMP.value.(x))
end

A workaround is to sort the data by the Unique ID and then setting the values of the optimization variables with unique ID equal to a variable t as shown below:

function run_model2()
generate_data()
sort!(df,"Unique_ID")
c = df.Values
model, x = build_model()
# Define a varible to set values of Unique ID equal 
@variable(model,t[1:2])
for i in 1:3
    @constraint(model,x[i]-t[1] ==0)
end
for i in 4:5
     @constraint(model,x[i]-t[2] ==0)
end
optimize!(model)
println("Results",JuMP.value.(x))
end

This is certainly not the best way of doing it and it is very manual. 1.What’s the recommended way of enforcing this constraint, ie. variables with the same unique ID should have same values?
I would like some (optional) constraints to be defined at the run time, such as some constraints to be applied only if an option is selected and/or values of some variables to be fixed when calling the run function. For example, I would like a constraint based on sector such that sum of values of x in a sector should not be greater than or equal to a value defined at the run time, and also the option to fix values of some variables. 2. How should I define the run function to define these optional constraints?

My attempts at doing it are given below, but it contains errors. I do not understand how to set the optional arguments of the run_model function to a default value and also that default value or a user defined value will only apply if argument is Yes for the if condition.

function run_model3(limit::string,fix::string)
generate_data()
if limit = "Yes"
    sort!(df,"Sector")
    c = df.Values
    model, x = build_model()
    @constraint(model,sum(x[i] for i =1:3)<=0.3)
    @constraint(model,sum(x[i] for i =4:9)<=0.3)
else
    c = df.Values
    model, x = build_model()
end
if fix="Yes"
    JuMP.fix.value(x[1]=0.1,force=true)
    c = df.Values
    model, x = build_model()
end
c = df.Values
model, x = build_model()
optimize!(model)
println("Results",JuMP.value.(x))
end

This should give you some ideas

using DataFrames
using JuMP
using Clp

function generate_data()
    df = DataFrame(
        "SN" => (1:10),
        "Unique_ID"=> ["A","B","A","B","C","D","E","F","G","A"],
        "Sector" => ["Priv", "Gov", "Utl", "Priv","Priv","Gov","Utl","Priv","Priv","Gov"],
        "Values" => rand(20.0:140.0, 10),
    )
    return df
end

function build_model(c)
    model = Model(Clp.Optimizer)
    @variable(model, x[1:length(c)] >=0 )
    @objective(model, Min, sum(c[i] * x[i] for i = 1:length(c)))
    @constraint(model, con1, sum(x[i] for i = 1:length(c)) == 1)
    @constraint(model, con2, sum(c[i] * x[i] for i = 1:length(c)) >= 100)
    return model, x
end

function run_model(; fix_values = false)
    df = generate_data()
    model, x = build_model(df.Values)
    unique_keys = unique(df.Unique_ID)
    @variable(model, t[unique_keys])
    @constraint(model, [i = 1:length(x)], x[i] == t[df.Unique_ID[i]])
    if fix_values
        fix(x[1], 0.1; force = true)
    end
    optimize!(model)
    println("Results", JuMP.value.(x))
end
1 Like

That’s awesome! Thank you.

This does helps me to figure out answers to most of my questions.

I have not described it as clearly as it could be, there is one point that is still bugging me. That is how to make the code flexible so that at run time it accepts arguments to fix values of x by a defined number. For example, at run time the some or all of the following optional arguments could be specified, which should then trigger the if statement in the code to fix the values:

  • x[1]=0.1,x[2:4]=0.2,x[5]=0.1, x[6:7]=0.2 or
  • x[3]=0.3

so the arguments are essentially a union of x[i]=fix_value1 , x[i:n]= fixed_value2

Do I need to define some custom types and use multiple dispatch, or wrap another function to deal with this? I am really lost!

I am wondering whether this should be asked as a separate question under the usage category as it may apply in broader situations and not just optimisation.

1 Like

What do you mean by run time, exactly?

If run time means the time after creating the model but before calling optimize!, then a simple JuMP.fix should suffice.

If run time means in the middle of an call to optimize!, then see this post of mine.

By run time, I mean when calling the function to execute the code, ie function run_model(; fix_values = false) in @odow response. Basically when calling this function, I would like to have flexibility to fix values (optional) of variables which are to be specified as arguments to this function-so one does not have to and manually code them in the function before calling optimize!

Could you check if this what you want?

function run_model(; vars_and_vals_to_fix = nothing)

and then

    ...
    if vars_and_vals_to_fix !== nothing
        vars, vals = vars_and_vals_to_fix
        fix.(vars, vals; force = true)
    end
    ...

to which you pass:

run_model(; vars_and_vals_to_fix = (x[1:7], [0.1, 0.2, 0.2, 0.2, 0.1, 0.2, 0.2]))
1 Like

This is closer to what I want. The run_model function should be able to accept the arguments as you have set out, and additionally a combination of a range of x such as x[1:7] and a specific value say x[8] can be passed. Some examples below:

run_model(; vars_and_vals_to_fix = (x[1:7], [0.1, 0.2, 0.2, 0.2, 0.1, 0.2,0.2],x[8],0.1))
run_model(; vars_and_vals_to_fix = (x[1:7], [0.1, 0.2, 0.2, 0.2, 0.1, 0.2,0.2],x[9:10],[0.1,0.1]))

Unless it is too much of an overhead, my suggestion is to keep my code the way I wrote, and expand the ranges:

run_model(; vars_and_vals_to_fix = (vcat(x[1:7], x[9:10]), vcat([0.1, 0.2, 0.2, 0.2, 0.1, 0.2,0.2],[0.1,0.1])))

or

run_model(; vars_and_vals_to_fix = (vcat(x[1:7], x[9:10]), [0.1, 0.2, 0.2, 0.2, 0.1, 0.2,0.2, 0.1,0.1]))

If the overhead become too much I would recommend looking into @view and lazy iterators, but keeping this way of doing the things.

1 Like

Thanks