mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 07:48:50 -06:00
Start implementation of circular model
This commit is contained in:
@@ -7,3 +7,4 @@ version = "0.1.0"
|
|||||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
module RELOG
|
module RELOG
|
||||||
|
|
||||||
include("instance/structs.jl")
|
include("instance/structs.jl")
|
||||||
|
|
||||||
include("instance/parse.jl")
|
include("instance/parse.jl")
|
||||||
|
include("model/jumpext.jl")
|
||||||
|
include("model/build.jl")
|
||||||
|
|
||||||
end # module RELOG
|
end # module RELOG
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ function parse(json)::Instance
|
|||||||
# Read products
|
# Read products
|
||||||
products = Product[]
|
products = Product[]
|
||||||
products_by_name = OrderedDict{String,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_cost = timeseries(pdict["transportation cost (\$/km/tonne)"])
|
||||||
tr_energy = timeseries(pdict["transportation energy (J/km/tonne)"])
|
tr_energy = timeseries(pdict["transportation energy (J/km/tonne)"])
|
||||||
tr_emissions = timeseries(pdict["transportation emissions (tonne/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)
|
push!(products, prod)
|
||||||
products_by_name[pname] = prod
|
products_by_name[name] = prod
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read centers
|
# Read centers
|
||||||
centers = Center[]
|
centers = Center[]
|
||||||
centers_by_name = OrderedDict{String,Center}()
|
centers_by_name = OrderedDict{String,Center}()
|
||||||
for (cname, cdict) in json["centers"]
|
for (name, cdict) in json["centers"]
|
||||||
latitude = cdict["latitude (deg)"]
|
latitude = cdict["latitude (deg)"]
|
||||||
longitude = cdict["longitude (deg)"]
|
longitude = cdict["longitude (deg)"]
|
||||||
input = nothing
|
input = nothing
|
||||||
@@ -52,6 +52,7 @@ function parse(json)::Instance
|
|||||||
disposal_cost = prod_dict("disposal cost (\$/tonne)", 0.0)
|
disposal_cost = prod_dict("disposal cost (\$/tonne)", 0.0)
|
||||||
|
|
||||||
center = Center(;
|
center = Center(;
|
||||||
|
name,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
input,
|
input,
|
||||||
@@ -65,12 +66,12 @@ function parse(json)::Instance
|
|||||||
disposal_limit,
|
disposal_limit,
|
||||||
)
|
)
|
||||||
push!(centers, center)
|
push!(centers, center)
|
||||||
centers_by_name[cname] = center
|
centers_by_name[name] = center
|
||||||
end
|
end
|
||||||
|
|
||||||
plants = Plant[]
|
plants = Plant[]
|
||||||
plants_by_name = OrderedDict{String,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}}(
|
prod_dict(key; scale = 1.0, null_val = Inf) = OrderedDict{Product,Vector{Float64}}(
|
||||||
products_by_name[p] => [
|
products_by_name[p] => [
|
||||||
v === nothing ? null_val : v * scale for v in timeseries(pdict[key][p])
|
v === nothing ? null_val : v * scale for v in timeseries(pdict[key][p])
|
||||||
@@ -100,6 +101,7 @@ function parse(json)::Instance
|
|||||||
end
|
end
|
||||||
|
|
||||||
plant = Plant(;
|
plant = Plant(;
|
||||||
|
name,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
input_mix,
|
input_mix,
|
||||||
@@ -113,7 +115,7 @@ function parse(json)::Instance
|
|||||||
initial_capacity,
|
initial_capacity,
|
||||||
)
|
)
|
||||||
push!(plants, plant)
|
push!(plants, plant)
|
||||||
plants_by_name[pname] = plant
|
plants_by_name[name] = plant
|
||||||
end
|
end
|
||||||
|
|
||||||
return Instance(;
|
return Instance(;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Base.@kwdef struct Product
|
|||||||
end
|
end
|
||||||
|
|
||||||
Base.@kwdef struct Center
|
Base.@kwdef struct Center
|
||||||
|
name::String
|
||||||
latitude::Float64
|
latitude::Float64
|
||||||
longitude::Float64
|
longitude::Float64
|
||||||
input::Union{Product,Nothing}
|
input::Union{Product,Nothing}
|
||||||
@@ -29,6 +30,7 @@ Base.@kwdef struct PlantCapacity
|
|||||||
end
|
end
|
||||||
|
|
||||||
Base.@kwdef struct Plant
|
Base.@kwdef struct Plant
|
||||||
|
name::String
|
||||||
latitude::Float64
|
latitude::Float64
|
||||||
longitude::Float64
|
longitude::Float64
|
||||||
input_mix::OrderedDict{Product,Vector{Float64}}
|
input_mix::OrderedDict{Product,Vector{Float64}}
|
||||||
|
|||||||
95
src/model/build.jl
Normal file
95
src/model/build.jl
Normal file
@@ -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
|
||||||
47
src/model/jumpext.jl
Normal file
47
src/model/jumpext.jl
Normal file
@@ -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
|
||||||
@@ -5,6 +5,7 @@ version = "0.1.0"
|
|||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
|
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
|
||||||
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
|
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
|
||||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
RELOG = "7cafaa7a-b311-45f0-b313-80bf15b5e5e5"
|
RELOG = "7cafaa7a-b311-45f0-b313-80bf15b5e5e5"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using RELOG
|
|||||||
using JuliaFormatter
|
using JuliaFormatter
|
||||||
|
|
||||||
include("instance/parse_test.jl")
|
include("instance/parse_test.jl")
|
||||||
|
include("model/build_test.jl")
|
||||||
|
|
||||||
basedir = dirname(@__FILE__)
|
basedir = dirname(@__FILE__)
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ function runtests()
|
|||||||
@testset "RELOG" begin
|
@testset "RELOG" begin
|
||||||
instance_parse_test_1()
|
instance_parse_test_1()
|
||||||
instance_parse_test_2()
|
instance_parse_test_2()
|
||||||
|
model_build_test()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
10
test/src/model/build_test.jl
Normal file
10
test/src/model/build_test.jl
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user