Hello,
I’m new to Jula/JuMP and working on a very large optimization model, which generation I aim to fully automatize based on sets, relations among sets and parameters provided externally. I developed a basic procedure for this purpose that mainly builds on storing variables, parameters and constraints within Indexed Tables to enable fast grouping and filtering. Since changing these things will be very time-consuming once I have coded the actual model, I hoped to get some feedback on this beforehand.
In the example, demand for a certain energy carrier has to be satisfied at any point of time by production from a set of technologies.
using JuMP
using JuliaDB
using GLPK
model = Model()
# Creation of parameters and mappings among sets
Mapping_dic = Dict{Symbol, Array{Union{Int,Array{Int,1}},1}}()
# sets of of timesteps and energy carriers
Mapping_dic[:Time] = Array(1:4200)
Mapping_dic[:Carrier] = Array(1:2)
# sets of technologies mapped to carriers, techs in the first subarray can produce the first carrier and so on
Mapping_dic[:Tech] = [[1,2,3,4],[2,3,5,6,7]]
# parameter with demand to be satisfied
Parameter_tab = table(repeat(Array(1:4100),2),vcat(fill(1,4100),fill(2,4100)),fill(3.0,8200),names=[:Time,:Carrier,:Val], pkey = [:Time, :Carrier])
# Creation of production variables based on Mapping_dic
Variable_info = VariableInfo(true, 0, false, NaN, false, NaN, false, NaN, false, false)
Variables_tab = table(Int16[], Int16[], Int16[], VariableRef[], names=[:Time, :Carrier, :Tech, :Ref], pkey = [:Time, :Carrier])
function generate_variables(model,Mapping_dic,Variables_tab,Variable_info)
for time::Int16 = Mapping_dic[:Time], carrier::Int16 = Mapping_dic[:Carrier], tech::Int16 = Mapping_dic[:Tech][carrier]
push!(rows(Variables_tab), (Time = time, Carrier = carrier, Tech = tech, Ref = JuMP.add_variable(model, JuMP.build_variable(error, Variable_info))))
end
return Variables_tab
end
Variables_tab = generate_variables(model,Mapping_dic,Variables_tab,Variable_info)
# Creation of constraints based on variabled generated earlier and parameter provided externally
Constraints_tab = table(Int16[], Int16[], ScalarConstraint[], names=[:Time, :Carrier, :Ref], pkey = [:Time, :Carrier])
ConstraintsInfo_tab = join(JuliaDB.groupby(unique, Variables_tab, (:Time, :Carrier), select=:Ref), Parameter_tab, how=:inner)
function generate_constraints(Constraints_tab,ConstraintsFil_tab)
for i::NamedTuple{(:Time, :Carrier, :unique, :Val),Tuple{Int64,Int64,Array{VariableRef,1},Float64}} in rows(ConstraintsFil_tab)
push!(rows(Constraints_tab), (Time = i[1], Carrier = i[2], Ref = @build_constraint(sum(i[3]) == i[4])))
end
return Constraints_tab
end
Constraints_tab = generate_constraints(Constraints_tab,ConstraintsInfo_tab)
# Adding all contraints to the actual model, add a dummy objective function and solve
for i in select(Constraints_tab,:Ref) JuMP.add_constraint(model,i) end
@objective(model, Min, Variables_tab[1][:Ref])
JuMP.optimize!(model,with_optimizer(GLPK.Optimizer))
The first part of the code, creation of mappings and parameters, is hardcoded for testing purposes and would be computed from input data in the actual model. As you can see, the nested loop within the generate_variables function builds on the mapping earlier and avoids generating unnecessary variables for non-existing technology/carrier combinations. Afterwards, grouping the created table of variables and joining with the parameter table allows the automatic generation of all relevant constraints.
I hope the example is clear. I thought about preallocating the Indexed tables instead of pushing to increase performance, but could not find any corresponding methods.