Skip to content

Commit

Permalink
Start implementation of circular model
Browse files Browse the repository at this point in the history
  • Loading branch information
iSoron committed Dec 6, 2023
1 parent 4947ad1 commit 0da66b5
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 8 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
3 changes: 2 additions & 1 deletion src/RELOG.jl
Original file line number Diff line number Diff line change
@@ -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
16 changes: 9 additions & 7 deletions src/instance/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,6 +52,7 @@ function parse(json)::Instance
disposal_cost = prod_dict("disposal cost (\$/tonne)", 0.0)

center = Center(;
name,
latitude,
longitude,
input,
Expand All @@ -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])
Expand Down Expand Up @@ -100,6 +101,7 @@ function parse(json)::Instance
end

plant = Plant(;
name,
latitude,
longitude,
input_mix,
Expand All @@ -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(;
Expand Down
2 changes: 2 additions & 0 deletions src/instance/structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Base.@kwdef struct Product
end

Base.@kwdef struct Center
name::String
latitude::Float64
longitude::Float64
input::Union{Product,Nothing}
Expand All @@ -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}}
Expand Down
95 changes: 95 additions & 0 deletions src/model/build.jl
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions src/model/jumpext.jl
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions test/src/RELOGT.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ using RELOG
using JuliaFormatter

include("instance/parse_test.jl")
include("model/build_test.jl")

basedir = dirname(@__FILE__)

Expand All @@ -16,6 +17,7 @@ function runtests()
@testset "RELOG" begin
instance_parse_test_1()
instance_parse_test_2()
model_build_test()
end
end

Expand Down
10 changes: 10 additions & 0 deletions test/src/model/build_test.jl
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 0da66b5

Please sign in to comment.