From 0da66b571a4cc50c7c41e19d96e20aa26f19506b Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 6 Dec 2023 09:27:37 -0600 Subject: [PATCH] Start implementation of circular model --- Project.toml | 1 + src/RELOG.jl | 3 +- src/instance/parse.jl | 16 +++--- src/instance/structs.jl | 2 + src/model/build.jl | 95 ++++++++++++++++++++++++++++++++++++ src/model/jumpext.jl | 47 ++++++++++++++++++ test/Project.toml | 1 + test/src/RELOGT.jl | 2 + test/src/model/build_test.jl | 10 ++++ 9 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 src/model/build.jl create mode 100644 src/model/jumpext.jl create mode 100644 test/src/model/build_test.jl diff --git a/Project.toml b/Project.toml index 9a79e3f..97551ee 100644 --- a/Project.toml +++ b/Project.toml @@ -7,3 +7,4 @@ version = "0.1.0" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" diff --git a/src/RELOG.jl b/src/RELOG.jl index 5f22396..63b494e 100644 --- a/src/RELOG.jl +++ b/src/RELOG.jl @@ -1,7 +1,8 @@ module RELOG include("instance/structs.jl") - include("instance/parse.jl") +include("model/jumpext.jl") +include("model/build.jl") end # module RELOG diff --git a/src/instance/parse.jl b/src/instance/parse.jl index 65c5a18..44a71c5 100644 --- a/src/instance/parse.jl +++ b/src/instance/parse.jl @@ -18,19 +18,19 @@ function parse(json)::Instance # Read products products = Product[] products_by_name = OrderedDict{String,Product}() - for (pname, pdict) in json["products"] + for (name, pdict) in json["products"] tr_cost = timeseries(pdict["transportation cost (\$/km/tonne)"]) tr_energy = timeseries(pdict["transportation energy (J/km/tonne)"]) tr_emissions = timeseries(pdict["transportation emissions (tonne/km/tonne)"]) - prod = Product(; name = pname, tr_cost, tr_energy, tr_emissions) + prod = Product(; name, tr_cost, tr_energy, tr_emissions) push!(products, prod) - products_by_name[pname] = prod + products_by_name[name] = prod end # Read centers centers = Center[] centers_by_name = OrderedDict{String,Center}() - for (cname, cdict) in json["centers"] + for (name, cdict) in json["centers"] latitude = cdict["latitude (deg)"] longitude = cdict["longitude (deg)"] input = nothing @@ -52,6 +52,7 @@ function parse(json)::Instance disposal_cost = prod_dict("disposal cost (\$/tonne)", 0.0) center = Center(; + name, latitude, longitude, input, @@ -65,12 +66,12 @@ function parse(json)::Instance disposal_limit, ) push!(centers, center) - centers_by_name[cname] = center + centers_by_name[name] = center end plants = Plant[] plants_by_name = OrderedDict{String,Plant}() - for (pname, pdict) in json["plants"] + for (name, pdict) in json["plants"] prod_dict(key; scale = 1.0, null_val = Inf) = OrderedDict{Product,Vector{Float64}}( products_by_name[p] => [ v === nothing ? null_val : v * scale for v in timeseries(pdict[key][p]) @@ -100,6 +101,7 @@ function parse(json)::Instance end plant = Plant(; + name, latitude, longitude, input_mix, @@ -113,7 +115,7 @@ function parse(json)::Instance initial_capacity, ) push!(plants, plant) - plants_by_name[pname] = plant + plants_by_name[name] = plant end return Instance(; diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 9a136e0..b1f0ece 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -8,6 +8,7 @@ Base.@kwdef struct Product end Base.@kwdef struct Center + name::String latitude::Float64 longitude::Float64 input::Union{Product,Nothing} @@ -29,6 +30,7 @@ Base.@kwdef struct PlantCapacity end Base.@kwdef struct Plant + name::String latitude::Float64 longitude::Float64 input_mix::OrderedDict{Product,Vector{Float64}} diff --git a/src/model/build.jl b/src/model/build.jl new file mode 100644 index 0000000..78edc3d --- /dev/null +++ b/src/model/build.jl @@ -0,0 +1,95 @@ +using JuMP + +function build_model(instance::Instance; optimizer, variable_names::Bool = false) + model = JuMP.Model(optimizer) + centers = instance.centers + products = instance.products + plants = instance.plants + T = 1:instance.time_horizon + + # Transportation edges + # ------------------------------------------------------------------------- + E = [] + for m in products + for p1 in plants + m ∉ keys(p1.output) || continue + + # Plant to plant + for p2 in plants + p1 != p2 || continue + m ∉ keys(p2.input_mix) || continue + push!(E, (p1, p2, m)) + end + + # Plant to center + for c in centers + m == c.input || continue + push!(E, (p1, c, m)) + end + end + + for c1 in centers + m ∈ c1.outputs || continue + + # Center to plant + for p in plants + m ∈ keys(p.input_mix) || continue + push!(E, (c1, p, m)) + end + + # Center to center + for c2 in centers + m == c2.input || continue + push!(E, (c1, c2, m)) + end + end + end + + + # Decision variables + # ------------------------------------------------------------------------- + + # Plant p is operational at time t + x = _init(model, :x) + for p in plants, t in T + x[p.name, t] = @variable(model, binary = true) + end + + # Amount of product m sent from center/plant u to center/plant v at time T + y = _init(model, :y) + for (p1, p2, m) in E, t in T + y[p1.name, p2.name, m.name, t] = @variable(model, lower_bound=0) + end + + # Amount of product m produced by plant/center at time T + z_prod = _init(model, :z_prod) + for p in plants, m in keys(p.output), t in T + z_prod[p.name, m.name, t] = @variable(model, lower_bound=0) + end + for c in centers, m in c.outputs, t in T + z_prod[c.name, m.name, t] = @variable(model, lower_bound=0) + end + + # Amount of product m disposed at plant/center p at time T + z_disp = _init(model, :z_disp) + for p in plants, m in keys(p.output), t in T + z_disp[p.name, m.name, t] = @variable(model, lower_bound=0) + end + for c in centers, m in c.outputs, t in T + z_disp[c.name, m.name, t] = @variable(model, lower_bound=0) + end + + + # Objective function + # ------------------------------------------------------------------------- + + + # Constraints + # ------------------------------------------------------------------------- + + + if variable_names + _set_names!(model) + end + return model +end diff --git a/src/model/jumpext.jl b/src/model/jumpext.jl new file mode 100644 index 0000000..edfbef6 --- /dev/null +++ b/src/model/jumpext.jl @@ -0,0 +1,47 @@ +# This file extends some JuMP functions so that decision variables can be safely +# replaced by (constant) floating point numbers. + +using Printf +using JuMP + +import JuMP: value, fix, set_name + +function value(x::Float64) + return x +end + +function fix(x::Float64, v::Float64; force) + return abs(x - v) < 1e-6 || error("Value mismatch: $x != $v") +end + +function set_name(x::Float64, n::String) + # nop +end + +function _init(model::JuMP.Model, key::Symbol)::OrderedDict + if !(key in keys(object_dictionary(model))) + model[key] = OrderedDict() + end + return model[key] +end + +function _set_names!(model::JuMP.Model) + @info "Setting variable and constraint names..." + time_varnames = @elapsed begin + _set_names!(object_dictionary(model)) + end + @info @sprintf("Set names in %.2f seconds", time_varnames) +end + +function _set_names!(dict::Dict) + for name in keys(dict) + dict[name] isa AbstractDict || continue + for idx in keys(dict[name]) + if dict[name][idx] isa AffExpr + continue + end + idx_str = join(map(string, idx), ",") + set_name(dict[name][idx], "$name[$idx_str]") + end + end +end \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index 8e87618..ee48dc3 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -5,6 +5,7 @@ version = "0.1.0" [deps] HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" RELOG = "7cafaa7a-b311-45f0-b313-80bf15b5e5e5" diff --git a/test/src/RELOGT.jl b/test/src/RELOGT.jl index 6039da3..fcee132 100644 --- a/test/src/RELOGT.jl +++ b/test/src/RELOGT.jl @@ -5,6 +5,7 @@ using RELOG using JuliaFormatter include("instance/parse_test.jl") +include("model/build_test.jl") basedir = dirname(@__FILE__) @@ -16,6 +17,7 @@ function runtests() @testset "RELOG" begin instance_parse_test_1() instance_parse_test_2() + model_build_test() end end diff --git a/test/src/model/build_test.jl b/test/src/model/build_test.jl new file mode 100644 index 0000000..13215da --- /dev/null +++ b/test/src/model/build_test.jl @@ -0,0 +1,10 @@ +using RELOG +using Test +using HiGHS +using JuMP + +function model_build_test() + instance = RELOG.parsefile(fixture("simple.json")) + model = RELOG.build_model(instance, optimizer=HiGHS.Optimizer, variable_names=true) + print(model) +end \ No newline at end of file