Seattle to Topeka (Multi-location SC)#
This is a rather famous multi-location supply chain.
There are a two sources and three sinks. The goal is to procure a commodity (\(r\)) in any of the source, transport it and dispatch it in (or from) the sink locations.
The data is as shown:
Plants |
New York |
Chicago |
Topeka |
Supply |
|---|---|---|---|---|
Seattle |
2.5 |
1.7 |
1.8 |
350 |
San Diego |
2.5 |
1.8 |
1.4 |
600 |
Demand |
325 |
300 |
275 |
Initialize the Model#
The locations can be conveniently generated using .declare(). The US Dollar will be used as currency (a measure of economic impact)
from energia import *
from itertools import product
m = Model()
m.declare(Location, ['seattle', 'sandiego', 'newyork', 'chicago', 'topeka'])
m.usd = Currency()
Resources#
There are three resources we consider.
\(r\_consume\) - resource pre-procurement
\(r\) - The insitu resource being transported
\(r\_release\) - resource post-dispatch
Consumption Upper Bounds#
Set the maximum consumption allowed at source locations
m.r_consume = Resource()
m.r_consume.consume(m.seattle) <= 350
m.r_consume.consume(m.sandiego) <= 600
⚖ Initiated r_consume balance in (seattle, t0) ⏱ 0.0001 s
🔗 Bound [≤] r_consume consume in (seattle, t0) ⏱ 0.0008 s
⚖ Initiated r_consume balance in (sandiego, t0) ⏱ 0.0001 s
🔗 Bound [≤] r_consume consume in (sandiego, t0) ⏱ 0.0006 s
Release Lower Bounds#
Set the minimum release allowed at sink locations
m.r_release = Resource()
m.r_release.release(m.newyork) >= 325
m.r_release.release(m.chicago) >= 300
m.r_release.release(m.topeka) >= 275
⚖ Initiated r_release balance in (newyork, t0) ⏱ 0.0001 s
🔗 Bound [≥] r_release release in (newyork, t0) ⏱ 0.0011 s
⚖ Initiated r_release balance in (chicago, t0) ⏱ 0.0001 s
🔗 Bound [≥] r_release release in (chicago, t0) ⏱ 0.0006 s
⚖ Initiated r_release balance in (topeka, t0) ⏱ 0.0001 s
🔗 Bound [≥] r_release release in (topeka, t0) ⏱ 0.0006 s
Insitu Resource#
This is the primary resource being transported. The other two resources are dummy resources created for convenience
m.r = Resource()
Processes#
We create two dummy processes:
Purchase - which expends \(r\_consume\) to produce \(r\)
Dispatch - which expends \(r\) to produce \(r\_release\)
These are located at the sources and sinks respectively
m.purchase = Process()
m.purchase(m.r) == -m.r_consume
m.purchase.operate == True
m.dispatch = Process()
m.dispatch(-m.r) == m.r_release
m.dispatch.operate == True
m.purchase.locate(m.seattle, m.sandiego)
m.dispatch.locate(m.newyork, m.chicago, m.topeka)
💡 Assumed purchase capacity unbounded in (seattle, t0) ⏱ 0.0001 s
🧭 Mapped space for operate (purchase, seattle, t0) ⟺ (purchase, ntw, t0) ⏱ 0.0004 s
🔗 Bound [≤] purchase operate in (seattle, t0) ⏱ 0.0011 s
💡 Assumed purchase operate bounded by capacity in (seattle, t0) ⏱ 0.0015 s
💡 Assumed purchase capacity unbounded in (sandiego, t0) ⏱ 0.0001 s
🧭 Mapped space for operate (purchase, sandiego, t0) ⟺ (purchase, ntw, t0) ⏱ 0.0001 s
🔗 Bound [≤] purchase operate in (sandiego, t0) ⏱ 0.0007 s
💡 Assumed purchase operate bounded by capacity in (sandiego, t0) ⏱ 0.0013 s
⚖ Initiated r balance in (seattle, t0) ⏱ 0.0001 s
🔗 Bound [=] r produce in (seattle, t0) ⏱ 0.0006 s
⚖ Updated r_consume balance with expend(r_consume, seattle, t0, operate, purchase) ⏱ 0.0001 s
🔗 Bound [=] r_consume expend in (seattle, t0) ⏱ 0.0005 s
⚖ Initiated r balance in (sandiego, t0) ⏱ 0.0001 s
🔗 Bound [=] r produce in (sandiego, t0) ⏱ 0.0007 s
⚖ Updated r_consume balance with expend(r_consume, sandiego, t0, operate, purchase) ⏱ 0.0001 s
🔗 Bound [=] r_consume expend in (sandiego, t0) ⏱ 0.0006 s
🏭 Operating streams introduced for purchase in seattle, sandiego ⏱ 0.0040 s
🏗 Construction streams introduced for purchase in seattle, sandiego ⏱ 0.0000 s
🌍 Located purchase in seattle, sandiego ⏱ 0.0091 s
💡 Assumed dispatch capacity unbounded in (newyork, t0) ⏱ 0.0002 s
🧭 Mapped space for operate (dispatch, newyork, t0) ⟺ (dispatch, ntw, t0) ⏱ 0.0001 s
🔗 Bound [≤] dispatch operate in (newyork, t0) ⏱ 0.0007 s
💡 Assumed dispatch operate bounded by capacity in (newyork, t0) ⏱ 0.0011 s
💡 Assumed dispatch capacity unbounded in (chicago, t0) ⏱ 0.0001 s
🧭 Mapped space for operate (dispatch, chicago, t0) ⟺ (dispatch, ntw, t0) ⏱ 0.0015 s
🔗 Bound [≤] dispatch operate in (chicago, t0) ⏱ 0.0021 s
💡 Assumed dispatch operate bounded by capacity in (chicago, t0) ⏱ 0.0024 s
💡 Assumed dispatch capacity unbounded in (topeka, t0) ⏱ 0.0002 s
🧭 Mapped space for operate (dispatch, topeka, t0) ⟺ (dispatch, ntw, t0) ⏱ 0.0002 s
🔗 Bound [≤] dispatch operate in (topeka, t0) ⏱ 0.0007 s
💡 Assumed dispatch operate bounded by capacity in (topeka, t0) ⏱ 0.0010 s
⚖ Initiated r balance in (newyork, t0) ⏱ 0.0002 s
🔗 Bound [=] r expend in (newyork, t0) ⏱ 0.0009 s
⚖ Updated r_release balance with produce(r_release, newyork, t0, operate, dispatch) ⏱ 0.0001 s
🔗 Bound [=] r_release produce in (newyork, t0) ⏱ 0.0010 s
⚖ Initiated r balance in (chicago, t0) ⏱ 0.0002 s
🔗 Bound [=] r expend in (chicago, t0) ⏱ 0.0011 s
⚖ Updated r_release balance with produce(r_release, chicago, t0, operate, dispatch) ⏱ 0.0002 s
🔗 Bound [=] r_release produce in (chicago, t0) ⏱ 0.0012 s
⚖ Initiated r balance in (topeka, t0) ⏱ 0.0001 s
🔗 Bound [=] r expend in (topeka, t0) ⏱ 0.0008 s
⚖ Updated r_release balance with produce(r_release, topeka, t0, operate, dispatch) ⏱ 0.0001 s
🔗 Bound [=] r_release produce in (topeka, t0) ⏱ 0.0012 s
🏭 Operating streams introduced for dispatch in newyork, chicago, topeka ⏱ 0.0103 s
🏗 Construction streams introduced for dispatch in newyork, chicago, topeka ⏱ 0.0000 s
🌍 Located dispatch in newyork, chicago, topeka ⏱ 0.0183 s
Linking Locations#
Since the linkages between sources and sinks are unique. We can use the .Link() method. A unique linkage between two locations can simply be accessed using: source - sink
For multiple linkages between two given locations. Named Linkage objects are needed.
dist_dict = {
m.seattle: {m.newyork: 2.5, m.chicago: 1.7, m.topeka: 1.8},
m.sandiego: {m.newyork: 2.5, m.chicago: 1.8, m.topeka: 1.4},
}
for i, j in product([m.seattle, m.sandiego], [m.newyork, m.chicago, m.topeka]):
m.Link(i, j, dist=dist_dict[i][j])
You can always check the linking
m.seattle.links(m.newyork) # Link from Seattle to New York
seattle is source and newyork is sink in seattle-newyork
[seattle-newyork]
m.sandiego.connected(m.newyork)
True
Transportation#
In this mickey-mouse problem there are no dependent resources (produced and expended) besides the primary resource being transported.
A constant cost of 90 \(\frac{\$}{\text{unit distance}}\) is considered
m.channel = Transport()
m.channel(m.r) == 1.0 # 100% efficient
for i in dist_dict:
for j in dist_dict[i]:
m.usd.spend(m.channel.operate, i - j) == 90
m.channel.locate(i - j)
🔗 Bound [=] usd spend in (seattle-newyork, t0) ⏱ 0.0005 s
💡 Assumed channel capacity unbounded in (seattle-newyork, t0) ⏱ 0.0002 s
🔗 Bound [≤] channel operate in (seattle-newyork, t0) ⏱ 0.0001 s
💡 Assumed channel operate bounded by capacity in (seattle-newyork, t0) ⏱ 0.0005 s
⚖ Updated r balance with ship_in(r, seattle-newyork, t0, operate, channel) ⏱ 0.0004 s
🔗 Bound [=] r ship_in in (seattle-newyork, t0) ⏱ 0.0014 s
⚖ Updated r balance with ship_out(r, seattle-newyork, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_out in (seattle-newyork, t0) ⏱ 0.0008 s
🏭 Operating streams introduced for channel in seattle-newyork ⏱ 0.0035 s
🏗 Construction streams introduced for channel in seattle-newyork ⏱ 0.0000 s
🌍 Located channel in seattle-newyork ⏱ 0.0056 s
🔗 Bound [=] usd spend in (seattle-chicago, t0) ⏱ 0.0003 s
💡 Assumed channel capacity unbounded in (seattle-chicago, t0) ⏱ 0.0002 s
🔗 Bound [≤] channel operate in (seattle-chicago, t0) ⏱ 0.0001 s
💡 Assumed channel operate bounded by capacity in (seattle-chicago, t0) ⏱ 0.0005 s
⚖ Updated r balance with ship_in(r, seattle-chicago, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_in in (seattle-chicago, t0) ⏱ 0.0009 s
⚖ Updated r balance with ship_out(r, seattle-chicago, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_out in (seattle-chicago, t0) ⏱ 0.0006 s
🏭 Operating streams introduced for channel in seattle-newyork, seattle-chicago ⏱ 0.0022 s
🏗 Construction streams introduced for channel in seattle-newyork, seattle-chicago ⏱ 0.0000 s
🌍 Located channel in seattle-chicago ⏱ 0.0037 s
🔗 Bound [=] usd spend in (seattle-topeka, t0) ⏱ 0.0002 s
💡 Assumed channel capacity unbounded in (seattle-topeka, t0) ⏱ 0.0002 s
🔗 Bound [≤] channel operate in (seattle-topeka, t0) ⏱ 0.0001 s
💡 Assumed channel operate bounded by capacity in (seattle-topeka, t0) ⏱ 0.0005 s
⚖ Updated r balance with ship_in(r, seattle-topeka, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_in in (seattle-topeka, t0) ⏱ 0.0007 s
⚖ Updated r balance with ship_out(r, seattle-topeka, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_out in (seattle-topeka, t0) ⏱ 0.0007 s
🏭 Operating streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka ⏱ 0.0022 s
🏗 Construction streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka ⏱ 0.0000 s
🌍 Located channel in seattle-topeka ⏱ 0.0037 s
🔗 Bound [=] usd spend in (sandiego-newyork, t0) ⏱ 0.0005 s
💡 Assumed channel capacity unbounded in (sandiego-newyork, t0) ⏱ 0.0002 s
🔗 Bound [≤] channel operate in (sandiego-newyork, t0) ⏱ 0.0001 s
💡 Assumed channel operate bounded by capacity in (sandiego-newyork, t0) ⏱ 0.0004 s
⚖ Updated r balance with ship_in(r, sandiego-newyork, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_in in (sandiego-newyork, t0) ⏱ 0.0007 s
⚖ Updated r balance with ship_out(r, sandiego-newyork, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_out in (sandiego-newyork, t0) ⏱ 0.0008 s
🏭 Operating streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka, sandiego-newyork ⏱ 0.0022 s
🏗 Construction streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka, sandiego-newyork ⏱ 0.0000 s
🌍 Located channel in sandiego-newyork ⏱ 0.0037 s
🔗 Bound [=] usd spend in (sandiego-chicago, t0) ⏱ 0.0003 s
💡 Assumed channel capacity unbounded in (sandiego-chicago, t0) ⏱ 0.0002 s
🔗 Bound [≤] channel operate in (sandiego-chicago, t0) ⏱ 0.0001 s
💡 Assumed channel operate bounded by capacity in (sandiego-chicago, t0) ⏱ 0.0004 s
⚖ Updated r balance with ship_in(r, sandiego-chicago, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_in in (sandiego-chicago, t0) ⏱ 0.0008 s
⚖ Updated r balance with ship_out(r, sandiego-chicago, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_out in (sandiego-chicago, t0) ⏱ 0.0008 s
🏭 Operating streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka, sandiego-newyork, sandiego-chicago ⏱ 0.0023 s
🏗 Construction streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka, sandiego-newyork, sandiego-chicago ⏱ 0.0000 s
🌍 Located channel in sandiego-chicago ⏱ 0.0039 s
🔗 Bound [=] usd spend in (sandiego-topeka, t0) ⏱ 0.0003 s
💡 Assumed channel capacity unbounded in (sandiego-topeka, t0) ⏱ 0.0002 s
🔗 Bound [≤] channel operate in (sandiego-topeka, t0) ⏱ 0.0001 s
💡 Assumed channel operate bounded by capacity in (sandiego-topeka, t0) ⏱ 0.0004 s
⚖ Updated r balance with ship_in(r, sandiego-topeka, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_in in (sandiego-topeka, t0) ⏱ 0.0008 s
⚖ Updated r balance with ship_out(r, sandiego-topeka, t0, operate, channel) ⏱ 0.0001 s
🔗 Bound [=] r ship_out in (sandiego-topeka, t0) ⏱ 0.0007 s
🏭 Operating streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka, sandiego-newyork, sandiego-chicago, sandiego-topeka ⏱ 0.0021 s
🏗 Construction streams introduced for channel in seattle-newyork, seattle-chicago, seattle-topeka, sandiego-newyork, sandiego-chicago, sandiego-topeka ⏱ 0.0000 s
🌍 Located channel in sandiego-topeka ⏱ 0.0037 s
The Formulation#
m.show(True)
Mathematical Program for Program(m)
Index Sets
s.t.
Balance Constraints
Binds Constraints
Calculations Constraints
Mapping Constraints
Optimize!#
The model can now be optimized
m.usd.spend.opt()
🧭 Mapped space for spend (usd, seattle-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, seattle-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, seattle-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, seattle-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, seattle-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, seattle-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, seattle-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, seattle-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-newyork, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, sandiego-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0003 s
🧭 Mapped space for spend (usd, sandiego-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-chicago, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, sandiego-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0002 s
🧭 Mapped space for spend (usd, sandiego-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
🧭 Mapped space for spend (usd, sandiego-topeka, t0, operate, channel) ⟺ (usd, ntw, t0) ⏱ 0.0001 s
📝 Generated Program(m).mps ⏱ 0.0026 s
Read MPS format model from file Program(m).mps
Reading time = 0.00 seconds
PROGRAM(M): 57 rows, 58 columns, 124 nonzeros
📝 Generated gurobipy model. See .formulation ⏱ 0.0046 s
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (win64 - Windows 11.0 (26100.2))
CPU model: 13th Gen Intel(R) Core(TM) i7-13700, instruction set [SSE2|AVX|AVX2]
Thread count: 16 physical cores, 24 logical processors, using up to 24 threads
Optimize a model with 57 rows, 58 columns and 124 nonzeros
Model fingerprint: 0x3106ff68
Coefficient statistics:
Matrix range [1e+00, 2e+02]
Objective range [1e+00, 1e+00]
Bounds range [0e+00, 0e+00]
RHS range [3e+02, 6e+02]
Presolve removed 52 rows and 52 columns
Presolve time: 0.00s
Presolved: 5 rows, 6 columns, 12 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 0.0000000e+00 1.125000e+02 0.000000e+00 0s
4 1.5367500e+05 0.000000e+00 0.000000e+00 0s
Solved in 4 iterations and 0.00 seconds (0.00 work units)
Optimal objective 1.536750000e+05
📝 Generated Solution object for Program(m). See .solution ⏱ 0.0003 s
✅ Program(m) optimized using gurobi. Display using .output() ⏱ 0.0117 s
Solution#
The solution pertaining to each aspect can be accessed individually.
For the whole solution, use Model.output()
Seattle serves New York and Chicago
Sandiego serves New York and Topeka
m.ship_in.output()
Exports (\(\mathbf{expt}\)) and Imports (\(\mathbf{impt}\)) should match!
m.ship_out.output()
Contribution to overall cost can be ascertained
m.spend.output()
Solution as Dictionary#
m.solution.asdict()
{'consume': [350.0, 550.0],
'release': [325.0, 300.0, 275.0],
'operate': [900.0,
900.0,
350.0,
550.0,
325.0,
300.0,
275.0,
50.0,
300.0,
0.0,
275.0,
0.0,
275.0],
'capacity': [350.0,
550.0,
325.0,
300.0,
275.0,
50.0,
300.0,
0.0,
275.0,
0.0,
275.0],
'produce': [350.0, 550.0, 325.0, 300.0, 275.0],
'expend': [350.0, 550.0, 325.0, 300.0, 275.0],
'spend': [11250.0, 45900.0, 0.0, 61875.0, 0.0, 34649.99999999999, 153675.0],
'ship_in': [50.0, 300.0, 0.0, 275.0, 0.0, 275.0],
'ship_out': [50.0, 300.0, 0.0, 275.0, 0.0, 275.0]}