diff --git a/Manifest.toml b/Manifest.toml index d8b02a7..b3e446d 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -11,9 +11,9 @@ version = "0.5.0" [[BinaryProvider]] deps = ["Libdl", "Logging", "SHA"] -git-tree-sha1 = "428e9106b1ff27593cbd979afac9b45b82372b8c" +git-tree-sha1 = "ecdec412a9abc8db54c0efc5548c64dfce072058" uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232" -version = "0.5.9" +version = "0.5.10" [[Bzip2_jll]] deps = ["Libdl", "Pkg"] @@ -21,6 +21,11 @@ git-tree-sha1 = "3663bfffede2ef41358b6fc2e1d8a6d50b3c3904" uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" version = "1.0.6+2" +[[CEnum]] +git-tree-sha1 = "1b77a77c3b28e0b3f413f7567c9bb8dd9bdccd14" +uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" +version = "0.3.0" + [[Calculus]] deps = ["LinearAlgebra"] git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad" @@ -33,6 +38,18 @@ git-tree-sha1 = "62d80f448b5d77b3f0a59cecf6197aad2a3aa280" uuid = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" version = "0.6.7" +[[Clp]] +deps = ["BinaryProvider", "CEnum", "Clp_jll", "Libdl", "MathOptInterface", "SparseArrays"] +git-tree-sha1 = "cb38198edf176b720461273fd4a6c678ef9b2ec5" +uuid = "e2554f3b-3117-50c0-817c-e040a3ddf72d" +version = "0.8.0" + +[[Clp_jll]] +deps = ["CoinUtils_jll", "CompilerSupportLibraries_jll", "Libdl", "OpenBLAS32_jll", "Osi_jll", "Pkg"] +git-tree-sha1 = "70fe9e52fd95fa37f645e3d30f08f436cc5b1457" +uuid = "06985876-5285-5a41-9fcb-8948a742cc53" +version = "1.17.6+5" + [[CodeTracking]] deps = ["InteractiveUtils", "UUIDs"] git-tree-sha1 = "cab4da992adc0a64f63fa30d2db2fd8bec40cab4" @@ -41,9 +58,9 @@ version = "0.5.11" [[CodecBzip2]] deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"] -git-tree-sha1 = "2fee975d68f9a8b22187ae86e33c0829b30cf231" +git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7" uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" -version = "0.7.1" +version = "0.7.2" [[CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] @@ -51,6 +68,12 @@ git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.0" +[[CoinUtils_jll]] +deps = ["CompilerSupportLibraries_jll", "Libdl", "OpenBLAS32_jll", "Pkg"] +git-tree-sha1 = "ee1f06ab89337b7f194c29377ab174e752cdf60d" +uuid = "be027038-0da8-5614-b30d-e42594cb92df" +version = "2.11.3+3" + [[CommonSubexpressions]] deps = ["Test"] git-tree-sha1 = "efdaf19ab11c7889334ca247ff4c9f7c322817b0" @@ -71,9 +94,9 @@ version = "0.6.0" [[DataStructures]] deps = ["InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "7d7578b00789cf16c5f68fad71868e773edd58a2" +git-tree-sha1 = "af6d9c86e191c917c2276fbede1137e8ea20157f" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.17.16" +version = "0.17.17" [[Dates]] deps = ["Printf"] @@ -112,9 +135,9 @@ version = "0.5.0" [[HTTP]] deps = ["Base64", "Dates", "IniFile", "MbedTLS", "Sockets"] -git-tree-sha1 = "fe31f4ff144392ad8176f5c7c03cca6ba320271c" +git-tree-sha1 = "ec87d5e2acbe1693789efbbe14f5ea7525758f71" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "0.8.14" +version = "0.8.15" [[IniFile]] deps = ["Test"] @@ -146,9 +169,9 @@ version = "0.21.2" [[JuliaInterpreter]] deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] -git-tree-sha1 = "4ab65b7deb5af83f022f26ad3351a3bd5d80c6e2" +git-tree-sha1 = "adfa56c6a1066d3baadb5d2a070d0f966d880a6d" uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" -version = "0.7.16" +version = "0.7.17" [[LibGit2]] deps = ["Printf"] @@ -212,6 +235,12 @@ git-tree-sha1 = "928b8ca9b2791081dc71a51c55347c27c618760f" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" version = "0.3.3" +[[OpenBLAS32_jll]] +deps = ["CompilerSupportLibraries_jll", "Libdl", "Pkg"] +git-tree-sha1 = "793b33911239d2651c356c823492b58d6490d36a" +uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2" +version = "0.3.9+4" + [[OpenSpecFun_jll]] deps = ["CompilerSupportLibraries_jll", "Libdl", "Pkg"] git-tree-sha1 = "d51c416559217d974a1113522d5919235ae67a87" @@ -223,6 +252,12 @@ git-tree-sha1 = "12ce190210d278e12644bcadf5b21cbdcf225cd3" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.2.0" +[[Osi_jll]] +deps = ["CoinUtils_jll", "CompilerSupportLibraries_jll", "Libdl", "OpenBLAS32_jll", "Pkg"] +git-tree-sha1 = "bd436a97280df40938e66ae8d18e57aceb072856" +uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd" +version = "0.108.5+3" + [[Parsers]] deps = ["Dates", "Test"] git-tree-sha1 = "f0abb338b4d00306500056a3fd44c221b8473ef2" @@ -253,9 +288,9 @@ uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[Revise]] deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Pkg", "REPL", "UUIDs", "Unicode"] -git-tree-sha1 = "3185d2ee31756af9e20ce045ddfaedcd0df9e4aa" +git-tree-sha1 = "adb8b66d5e53151628a9bcf51049ed70c8fa7626" uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" -version = "2.6.6" +version = "2.7.0" [[SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" diff --git a/Project.toml b/Project.toml index 36f472a..053106d 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.1.0" [deps] Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" +Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d" Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" diff --git a/instances/solutions/s1.json b/instances/solutions/s1.json index f9ea945..9737c58 100644 --- a/instances/solutions/s1.json +++ b/instances/solutions/s1.json @@ -47,8 +47,8 @@ 0.0 ], "capacity": [ - 1.0e8, - 1.0e8 + 15000.0, + 15000.0 ] } }, @@ -66,8 +66,8 @@ }, "latitude": 0.0, "expansion cost": [ - 750.0, - 0.0 + 0.0, + 750.0 ], "output": { "dispose": { @@ -134,8 +134,8 @@ "Origin": { "C3": { "amount": [ - 212.97, - 212.97 + 212.97000000000003, + 212.97000000000003 ], "latitude": 84.0, "distance": 8889.75, @@ -145,8 +145,8 @@ 6389.1 ], "transportation cost": [ - 3.19455, - 3.19455 + 3.1945500000000004, + 3.1945500000000004 ] }, "C7": { @@ -168,19 +168,19 @@ }, "C5": { "amount": [ - 510.33, - 510.33 + 510.3299999999999, + 510.3299999999999 ], "latitude": 32.0, "distance": 9148.52, "longitude": 92.0, "variable operating cost": [ - 15309.9, - 15309.9 + 15309.899999999998, + 15309.899999999998 ], "transportation cost": [ - 7.6549499999999995, - 7.6549499999999995 + 7.654949999999999, + 7.654949999999999 ] }, "C9": { @@ -218,14 +218,14 @@ 747.728 ], "P3": [ - 1869.3199999999997, - 1869.3199999999997 + 1869.32, + 1869.32 ] }, "latitude": 0.5, "expansion cost": [ - 3738.6399999999967, - 0.0 + 0.0, + 3364.7759999999994 ], "output": { "dispose": {}, @@ -247,8 +247,8 @@ "F4": { "L6": { "amount": [ - 1869.3199999999997, - 1869.3199999999997 + 1869.32, + 1869.32 ], "latitude": 50.0, "distance": 6828.89, @@ -391,12 +391,12 @@ } }, "opening cost": [ - 1000.0, + 999.9999999999999, 0.0 ], "capacity": [ 3738.6399999999994, - 3738.6399999999967 + 3738.6399999999994 ] } }, @@ -496,12 +496,12 @@ } }, "opening cost": [ - 3000.0, + 2999.9999999999995, 0.0 ], "capacity": [ - 946.728, - 1.0e8 + 10000.0, + 10000.0 ] } }, @@ -547,19 +547,19 @@ }, "L2": { "amount": [ - 1869.3199999999997, - 1869.3199999999997 + 1869.32, + 1869.32 ], "latitude": 0.5, "distance": 6828.89, "longitude": 0.5, "variable operating cost": [ - -28039.799999999996, - -28039.799999999996 + -28039.8, + -28039.8 ], "transportation cost": [ - 23.3665, - 23.3665 + 23.366500000000002, + 23.366500000000002 ] } }, @@ -588,8 +588,72 @@ 0.0 ], "capacity": [ - 1.0e8, - 1.0e8 + 10000.0, + 10000.0 + ] + } + } + }, + "products": { + "P1": { + "C1": { + "marginal cost": [ + 133.59, + 134.49 + ] + }, + "C2": { + "marginal cost": [ + 150.81, + 151.71 + ] + }, + "C3": { + "marginal cost": [ + 250.83, + 251.73 + ] + }, + "C8": { + "marginal cost": [ + 199.65, + 200.55 + ] + }, + "C6": { + "marginal cost": [ + 217.26, + 218.16 + ] + }, + "C10": { + "marginal cost": [ + 208.54, + 209.44 + ] + }, + "C4": { + "marginal cost": [ + 160.36, + 161.26 + ] + }, + "C5": { + "marginal cost": [ + 254.71, + 255.61 + ] + }, + "C7": { + "marginal cost": [ + 245.38, + 246.28 + ] + }, + "C9": { + "marginal cost": [ + 240.5, + 241.4 ] } } @@ -600,8 +664,8 @@ 0.0 ], "expansion": [ - 4488.639999999997, - 0.0 + 0.0, + 4114.776 ], "variable operating": [ 216672.818, @@ -612,8 +676,8 @@ 130.0 ], "total": [ - 225904.922057, - 216916.282057 + 221416.282057, + 221031.05805700002 ], "transportation": [ 133.464057, diff --git a/instances/solutions/s1.log b/instances/solutions/s1.log index 3e24158..f28b916 100644 --- a/instances/solutions/s1.log +++ b/instances/solutions/s1.log @@ -1,36 +1,56 @@ -Gurobi Optimizer version 9.0.0 build v9.0.0rc2 (linux64) -Optimize a model with 112 rows, 141 columns and 378 nonzeros -Model fingerprint: 0xf1c6e6cc -Variable types: 117 continuous, 24 integer (24 binary) -Coefficient statistics: - Matrix range [5e-02, 1e+08] - Objective range [1e+00, 3e+03] - Bounds range [1e+00, 1e+08] - RHS range [3e+01, 1e+08] -Found heuristic solution: objective 2000175.3046 -Presolve removed 89 rows and 110 columns -Presolve time: 0.00s -Presolved: 23 rows, 31 columns, 68 nonzeros -Found heuristic solution: objective 1999822.1568 -Variable types: 25 continuous, 6 integer (6 binary) +Welcome to the CBC MILP Solver +Version: 2.10.3 +Build Date: Oct 7 2019 -Root relaxation: objective 1.871010e+06, 9 iterations, 0.00 seconds +command line - Cbc_C_Interface -solve -quit (default strategy 1) +Continuous objective value is 1.86768e+06 - 0.00 seconds +Cgl0003I 7 fixed, 0 tightened bounds, 9 strengthened rows, 0 substitutions +Cgl0003I 0 fixed, 0 tightened bounds, 2 strengthened rows, 0 substitutions +Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions +Cgl0004I processed model has 35 rows, 55 columns (9 integer (9 of which binary)) and 211 elements +Cbc0012I Integer solution of 1871179 found by DiveCoefficient after 0 iterations and 0 nodes (0.00 seconds) +Cbc0038I Full problem 35 rows 55 columns, reduced to 18 rows 37 columns +Cbc0006I The LP relaxation is infeasible or too expensive +Cbc0013I At root node, 0 cuts changed objective from 1869627.3 to 1871179 in 1 passes +Cbc0014I Cut generator 0 (Probing) - 0 row cuts average 0.0 elements, 1 column cuts (1 active) in 0.000 seconds - new frequency is 1 +Cbc0014I Cut generator 1 (Gomory) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0014I Cut generator 4 (MixedIntegerRounding2) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0014I Cut generator 5 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0014I Cut generator 6 (TwoMirCuts) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0014I Cut generator 7 (ZeroHalf) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100 +Cbc0001I Search completed - best objective 1871178.961662621, took 2 iterations and 0 nodes (0.00 seconds) +Cbc0035I Maximum depth 0, 5 variables fixed on reduced cost +Cuts at root node changed objective from 1.86963e+06 to 1.87118e+06 +Probing was tried 1 times and created 1 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) +ZeroHalf was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds) - Nodes | Current Node | Objective Bounds | Work - Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time +Result - Optimal solution found - 0 0 1871009.93 0 2 1999822.16 1871009.93 6.44% - 0s -H 0 0 1871552.8257 1871009.93 0.03% - 0s +Objective value: 1871178.96166262 +Enumerated nodes: 0 +Total iterations: 2 +Time (CPU seconds): 0.01 +Time (Wallclock seconds): 0.01 -Explored 1 nodes (9 simplex iterations) in 0.00 seconds -Thread count was 4 (of 16 available processors) +Total time (CPU seconds): 0.01 (Wallclock seconds): 0.01 -Solution count 3: 1.87155e+06 1.99982e+06 2.00018e+06 - -Optimal solution found (tolerance 1.00e-02) -Best objective 1.871552825663e+06, best bound 1.871009926997e+06, gap 0.0290% +Coin0506I Presolve 24 (-100) rows, 38 (-103) columns and 164 (-236) elements +Clp0006I 0 Obj 1590708.5 Primal inf 13041.309 (4) Dual inf 429.24088 (24) +Clp0006I 16 Obj 1871179 +Clp0000I Optimal - objective value 1871179 +Coin0511I After Postsolve, objective 1871179, infeasibilities - dual 0 (0), primal 0 (0) +Clp0032I Optimal objective 1871178.962 - 16 iterations time 0.002, Presolve 0.00 Reading s1.json... Building graph... Building optimization model... -Optimizing... +Optimizing MILP... +Re-optimizing with integer variables fixed... Extracting solution... diff --git a/src/model.jl b/src/model.jl index 5b9212f..700dd4e 100644 --- a/src/model.jl +++ b/src/model.jl @@ -2,19 +2,20 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -using JuMP, LinearAlgebra, Geodesy, Cbc, ProgressBars +using JuMP, LinearAlgebra, Geodesy, Cbc, Clp, ProgressBars mutable struct ManufacturingModel mip::JuMP.Model vars::DotDict + eqs::DotDict instance::Instance graph::Graph end function build_model(instance::Instance, graph::Graph, optimizer)::ManufacturingModel - model = ManufacturingModel(Model(optimizer), DotDict(), instance, graph) + model = ManufacturingModel(Model(optimizer), DotDict(), DotDict(), instance, graph) create_vars!(model) create_objective_function!(model) create_shipping_node_constraints!(model) @@ -133,11 +134,16 @@ end function create_shipping_node_constraints!(model::ManufacturingModel) mip, vars, graph, T = model.mip, model.vars, model.graph, model.instance.time + eqs = model.eqs + + eqs.balance = Dict() for t in 1:T # Collection centers for n in graph.collection_shipping_nodes - @constraint(mip, sum(vars.flow[a, t] for a in n.outgoing_arcs) == n.location.amount[t]) + eqs.balance[n, t] = @constraint(mip, + sum(vars.flow[a, t] for a in n.outgoing_arcs) + == n.location.amount[t]) end # Plants @@ -188,7 +194,7 @@ function create_process_node_constraints!(model::ManufacturingModel) end end -function solve(filename::String; optimizer=Cbc.Optimizer) +function solve(filename::String; optimizer=Cbc.Optimizer, lp_optimizer=Clp.Optimizer) println("Reading $filename...") instance = RELOG.load(filename) @@ -198,7 +204,19 @@ function solve(filename::String; optimizer=Cbc.Optimizer) println("Building optimization model...") model = RELOG.build_model(instance, graph, optimizer) - println("Optimizing...") + println("Optimizing MILP...") + JuMP.optimize!(model.mip) + + println("Re-optimizing with integer variables fixed...") + all_vars = JuMP.all_variables(model.mip) + vals = Dict(var => JuMP.value(var) for var in all_vars) + JuMP.set_optimizer(model.mip, lp_optimizer) + for var in all_vars + if JuMP.is_binary(var) + JuMP.unset_binary(var) + JuMP.fix(var, vals[var]) + end + end JuMP.optimize!(model.mip) println("Extracting solution...") @@ -206,11 +224,12 @@ function solve(filename::String; optimizer=Cbc.Optimizer) end function get_solution(model::ManufacturingModel) - mip, vars, graph, instance = model.mip, model.vars, model.graph, model.instance + mip, vars, eqs, graph, instance = model.mip, model.vars, model.eqs, model.graph, model.instance T = instance.time output = Dict( "plants" => Dict(), + "products" => Dict(), "costs" => Dict( "fixed operating" => zeros(T), "variable operating" => zeros(T), @@ -231,6 +250,19 @@ function get_solution(model::ManufacturingModel) end end + # Products + for n in graph.collection_shipping_nodes + location_dict = Dict{Any, Any}( + "marginal cost" => [round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits=2) + for t in 1:T], + ) + if n.product.name ∉ keys(output["products"]) + output["products"][n.product.name] = Dict() + end + output["products"][n.product.name][n.location.name] = location_dict + end + + # Plants for plant in instance.plants skip_plant = true process_node = plant_to_process_node[plant] diff --git a/test/model_test.jl b/test/model_test.jl index fdb3558..e58a525 100644 --- a/test/model_test.jl +++ b/test/model_test.jl @@ -39,7 +39,6 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats dest = FileFormats.Model(format = FileFormats.FORMAT_LP) MOI.copy_to(dest, model.mip) MOI.write_to_file(dest, "model.lp") - end @testset "solve" begin