Examples¶
Ex: FTL Location¶
Where shSuld a DC be located to minimize transportation costs, given:
- FTLs containing a mix of products A and B shipped P2P from DC to customers in Winston-Salem, Durham, and Wilmington
- Each customer receives 20, 30, and 50% of total demand
- 100 tons/yr of A shipped FTL P2P to DC from a supplier in Asheville
- 380 tons/yr of B shipped FTL P2P to DC from Statesville
- Each carton of A weighs 30 lb, and occupies 10 ft3
- Each carton of B weighs 120 lb, and occupies 4 ft3
- Revenue per loaded truck mile is \$2
- Each truck’s cubic and weight capacity is 2,750 ft3 and 25 tons, respectively
using DataFrames, Optim
maxpayld(sh, tr) = min(tr.Kwt, sh.s*tr.Kcu/2000)
d1(x1, x2) = length(x1) == length(x2) ? sum(abs.(x1 .- x2)) :
error("Inputs not same length.")
D1(X₁, X₂) = [d1(i, j) for i in eachrow(X₁), j in eachrow(X₂)]
tr = (r = 2, Kwt = 25, Kcu = 2750)
uwt = [30, 120]
ucu = [10, 4]
shS = DataFrame(f=[100, 380], s=uwt./ucu)
pct = [20, 30, 50]/100
sh = vcat(shS, DataFrame(f=sum(shS.f)*pct, s=sum(shS.f)./sum(shS.f./shS.s)))
sh.qmax = [maxpayld(sh, tr) for sh in eachrow(sh)]
sh
| Row | f | s | qmax |
|---|---|---|---|
| Float64 | Float64 | Float64 | |
| 1 | 100.0 | 3.0 | 4.125 |
| 2 | 380.0 | 30.0 | 25.0 |
| 3 | 96.0 | 10.4348 | 14.3478 |
| 4 | 144.0 | 10.4348 | 14.3478 |
| 5 | 240.0 | 10.4348 | 14.3478 |
FTL Min TC: Locate DC to min transport cost assuming FTL used for all transport
using Statistics
n = sh.f./sh.qmax # TL/yr
w = n*tr.r # $/TL-mi
P = [50 150 190 270 420]'
xᵒ = optimize(x -> sum(w .* d1.(x, P)), [mean(P)]).minimizer[1]
150.00000000228758
hcat(sh, DataFrame(n=n, w=w))
| Row | f | s | qmax | n | w |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 100.0 | 3.0 | 4.125 | 24.2424 | 48.4848 |
| 2 | 380.0 | 30.0 | 25.0 | 15.2 | 30.4 |
| 3 | 96.0 | 10.4348 | 14.3478 | 6.69091 | 13.3818 |
| 4 | 144.0 | 10.4348 | 14.3478 | 10.0364 | 20.0727 |
| 5 | 240.0 | 10.4348 | 14.3478 | 16.7273 | 33.4545 |
(Extension) FTL Min TC w Freq Constr: Include monthly outbound frequency constraint: Outbound shipments must occur at least once each month
idxin, idxout = 1:2, 3:5
tmax = 1/12 # yr/TL
nmin = 1/tmax # TL/yr
n′ = [n[idxin]; max.(n[idxout], nmin)] # TL/yr
w′ = n′*tr.r # $/TL-mi
hcat(sh, DataFrame(n′=n′, w′=w′))
| Row | f | s | qmax | n′ | w′ |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 100.0 | 3.0 | 4.125 | 24.2424 | 48.4848 |
| 2 | 380.0 | 30.0 | 25.0 | 15.2 | 30.4 |
| 3 | 96.0 | 10.4348 | 14.3478 | 12.0 | 24.0 |
| 4 | 144.0 | 10.4348 | 14.3478 | 12.0 | 24.0 |
| 5 | 240.0 | 10.4348 | 14.3478 | 16.7273 | 33.4545 |
xᵒ′ = optimize(x -> sum(w′ .* d1.(x, P)), [mean(P)]).minimizer[1]
189.9999999907548
# Increase in number of shipments:
sum(n), sum(n′), sum(n′) - sum(n)
(72.89696969696969, 80.16969696969696, 7.272727272727266)
Impact of Freq Constr: In addition to the change of location, the total transport costs increase due to the increase in the number of shipments. Could also calculate the increase in cost, but this would require having to determine distances, which is tedious if working by hand.
Ex: TLC Location with Transshipment¶
TL Min TLC: Continuing with Ex: FTL Location, locate DC to minimize TLC assuming all shipments are TL and using either (a) uncoordinated inventory or (b) perfect cross-docking for transshipment at the DC.
(a) Uncoordinated inventory (UC):
uval = [300, 450] # unit value ($)
shS.v .= uval./(uwt/2000)
αS = 0 # batch prod
shS.α .= αS + 0.5 # uncoord DC
shC = DataFrame(f=sum(shS.f)*pct,
s=sum(shS.f)./sum(shS.f./shS.s),
v=sum(shS.f.*shS.v)/sum(shS.f))
shC.α .= 0.5 # constant consumption
sh = vcat(shS, shC)
sh.h .= 0.3
sh
| Row | f | s | v | α | h |
|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 100.0 | 3.0 | 20000.0 | 0.5 | 0.3 |
| 2 | 380.0 | 30.0 | 7500.0 | 0.5 | 0.3 |
| 3 | 96.0 | 10.4348 | 10104.2 | 0.5 | 0.3 |
| 4 | 144.0 | 10.4348 | 10104.2 | 0.5 | 0.3 |
| 5 | 240.0 | 10.4348 | 10104.2 | 0.5 | 0.3 |
# Add 'd' to sh based on location `x`
shd(x) = hcat(sh, DataFrame(d=[d1.(x, P[idxin]); d1.(x, P[idxout])]))
shd([0.])
| Row | f | s | v | α | h | d |
|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 100.0 | 3.0 | 20000.0 | 0.5 | 0.3 | 50.0 |
| 2 | 380.0 | 30.0 | 7500.0 | 0.5 | 0.3 | 150.0 |
| 3 | 96.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 190.0 |
| 4 | 144.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 270.0 |
| 5 | 240.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 420.0 |
Re-define functions from Tran 3, where tranchgTL is 0 if the distance of a shipment is 0 to allow an EF to be located at an EF and thus incur no transport charges. This assumes all EFs are actual facilities and not aggregate demand points.
maxpayld(sh, tr) = min(tr.Kwt, sh.s*tr.Kcu/2000)
# TL transport charge: charge is 0 if distance ≈ 0
tranchgTL(q, sh, tr) = isapprox(sh.d, 0., atol=1e-4) ? 0. :
max(ceil(q/maxpayld(sh, tr))*sh.d*tr.r, 45tr.r/2)
rateLTL(q,s,d,ppi) = ppi*(s^2/8 + 14)/((q^(1/7) * d^(15/29) - 7/2) * (s^2 + 2*s + 14))
tranchgLTL(q, sh, ppi) = max(rateLTL(q, sh.s, sh.d, ppi) * q * sh.d,
(ppi/104.2)*(45 + sh.d^(28/19)/1625))
totlogcost(q, c, sh) = sh.f*c/q + sh.α*sh.v*sh.h*q
# Determine independent shipment size that minimizes TLC
function minTLC(sh, tr = nothing, ppi = nothing)
qᵒ, TLCᵒ, isLTL = nothing, Inf, false
if tr !== nothing # Check TL option
qTL = min(sqrt((sh.f * max(tr.r*sh.d, 45tr.r/2))/(sh.α*sh.v*sh.h)),
maxpayld(sh, tr))
TLCtl = totlogcost(qTL, tranchgTL(qTL, sh, tr), sh)
qᵒ, TLCᵒ = qTL, TLCtl
end
if ppi !== nothing # Check LTL option
qLTL = optimize(q -> totlogcost(q, tranchgLTL(q,sh,ppi), sh),
150/2000, min(5,650sh.s/2000)).minimizer
TLCltl = totlogcost(qLTL, tranchgLTL(qLTL, sh, ppi), sh)
if TLCltl < TLCᵒ
qᵒ, TLCᵒ, isLTL = qLTL, TLCltl, true
end
end
return (qᵒ = qᵒ, TLCᵒ = TLCᵒ, isLTL = isLTL)
end;
TLCh(x) = sum(sh -> minTLC(sh, tr).TLCᵒ, eachrow(shd(x)))
TLCh([0.])
105147.58927589661
using CairoMakie
dcf() = display(current_figure())
x = 0:.1:450
lines(x, [TLCh([i]) for i in x])
dcf();
Continuous optimization has difficulty finding xᵒ = 150 (Statesville).
xˣ = optimize(TLCh, [mean(P)]).minimizer[1]
TLCh(xˣ), xˣ
(70920.5813093357, 172.4999999998286)
xᵒ = 150
TLCh(xᵒ), xᵒ
(69206.62608617966, 150)
sh = shd(xᵒ)
sh.qmax = [maxpayld(sh, tr) for sh in eachrow(sh)]
transform!(sh, AsTable(:) => ByRow(row -> minTLC(row, tr)) => AsTable)
| Row | f | s | v | α | h | d | qmax | qᵒ | TLCᵒ | isLTL |
|---|---|---|---|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Int64 | Float64 | Float64 | Float64 | Bool | |
| 1 | 100.0 | 3.0 | 20000.0 | 0.5 | 0.3 | 100 | 4.125 | 2.58199 | 15491.9 | false |
| 2 | 380.0 | 30.0 | 7500.0 | 0.5 | 0.3 | 0 | 25.0 | 3.89872 | 4386.06 | false |
| 3 | 96.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 40 | 14.3478 | 2.25105 | 6823.49 | false |
| 4 | 144.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 120 | 14.3478 | 4.77519 | 14474.8 | false |
| 5 | 240.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 270 | 14.3478 | 9.24712 | 28030.3 | false |
(b) Perfect cross-docking (XD): To simplify, we will use the optimal UC location, which may not be optimal for XD. As a result, we can use the final sh from UC since it has a column d with the distance from the optimal UC location to each EF. sh is copied to a new data frame shXD so that the α column can reflect cross-docking (can't just add another column to sh because totlogcost is looking for the α column).
shXD = sh[:, Not([:qᵒ, :TLCᵒ, :isLTL])]
shXD[idxin, :α] .= αS # Outbound α is unchanged
shXD
| Row | f | s | v | α | h | d | qmax |
|---|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Int64 | Float64 | |
| 1 | 100.0 | 3.0 | 20000.0 | 0.0 | 0.3 | 100 | 4.125 |
| 2 | 380.0 | 30.0 | 7500.0 | 0.0 | 0.3 | 0 | 25.0 |
| 3 | 96.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 40 | 14.3478 |
| 4 | 144.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 120 | 14.3478 |
| 5 | 240.0 | 10.4348 | 10104.2 | 0.5 | 0.3 | 270 | 14.3478 |
TLC_XDh(t) = sum(sh -> totlogcost(sh.f*t[1], tranchgTL(sh.f*t[1], sh, tr), sh),
eachrow(shXD))
t₀ = maximum(shXD.qmax./shXD.f)
TLC_XDh(t₀), t₀, 365.25t₀
(128668.52865612647, 0.14945652173913043, 54.58899456521739)
tᵒ = optimize(TLC_XDh, [t₀]).minimizer[1] # 1-D Nelder-Mead
TLC_XDh(tᵒ), tᵒ, 365.25tᵒ
(-Inf, -4.2075327623855226e302, -1.5368013414613122e305)
tᵒ = optimize(TLC_XDh, 0, t₀).minimizer[1] # 1-D bounded search (univariate opt)
TLC_XDh(tᵒ), tᵒ, 365.25tᵒ
(55539.17536298139, 0.03817125453381726, 13.942050718476755)
shXD.qXD = [sh.f*tᵒ for sh in eachrow(shXD)]
shXD.TLC_XD = [totlogcost(sh.f*tᵒ, tranchgTL(sh.f*tᵒ, sh, tr), sh)
for sh in eachrow(shXD)];
Determining Optimal Location: Determining the optimal location would turn the problem into a 2-D optimization problem even though the locations are in 1-D, where both the location x and the shipment interval t that minimizes total logistics cost would need to be determined.
Allocated FTL: Determine TLC assuming allocated FTL charge applied to qXD from optimal XD solution.
shXD.TLC_AFTL = [sh.f*tr.r*sh.d/sh.qmax + sh.α*sh.v*sh.h*sh.qXD for sh in eachrow(shXD)]
sum(shXD.TLC_AFTL)
44594.79979456418
Report results:
hcat(sh[:, Not([:s, :v, :α, :h, :d, :isLTL])],
shXD[:, Not([:f, :s, :v, :α, :h, :d, :qmax])])
| Row | f | qmax | qᵒ | TLCᵒ | qXD | TLC_XD | TLC_AFTL |
|---|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 100.0 | 4.125 | 2.58199 | 15491.9 | 3.81713 | 5239.54 | 4848.48 |
| 2 | 380.0 | 25.0 | 3.89872 | 4386.06 | 14.5051 | 0.0 | 0.0 |
| 3 | 96.0 | 14.3478 | 2.25105 | 6823.49 | 3.66444 | 7649.74 | 6089.19 |
| 4 | 144.0 | 14.3478 | 4.77519 | 14474.8 | 5.49666 | 14618.3 | 10739.6 |
| 5 | 240.0 | 14.3478 | 9.24712 | 28030.3 | 9.1611 | 28031.6 | 22917.5 |
using Printf
function prt(value, label, units="", digits=2)
fmtval = split(Printf.format(Printf.Format("%."*string(digits)*"f"),value),".")
fmtval[1] = reverse(join(Iterators.partition(reverse(fmtval[1]), 3), ","))
println("$label\t= $(join(fmtval, ".")) $units")
return value
end
prt( sum(sh.TLCᵒ), "Uncooridinated ", "(\$/yr)")
prt( sum(shXD.TLC_XD), "Perfect Cross-Docking", "(\$/yr)")
prt( sum(shXD.TLC_AFTL), "Allocated FTL ", "(\$/yr)");
Uncooridinated = 69,206.63 ($/yr) Perfect Cross-Docking = 55,539.18 ($/yr) Allocated FTL = 44,594.80 ($/yr)
Ex: Direct vs. Transshipment¶
using Logjam.DataTools, DataFrames
function dgc(xy₁, xy₂; unit=:mi)
length(xy₁) == length(xy₂) == 2 || error("Inputs must have length 2.")
unit in [:mi, :km] || error("Unit must be :mi or :km")
Δx, Δy = xy₂[1] - xy₁[1], xy₂[2] - xy₁[2]
a = sind(Δy / 2)^2 + cosd(xy₁[2]) * cosd(xy₂[2]) * sind(Δx / 2)^2
2 * asin(min(sqrt(a), 1.0)) * (unit == :mi ? 3958.75 : 6371.00)
end
Dgc(X₁, X₂) = [dgc(i, j) for i in eachrow(X₁), j in eachrow(X₂)]
function name2lonlat(name, st, df)
idx = findfirst(r -> startswith(r[:NAME], name) && r.ST == st, eachrow(df))
if idx === nothing
error("'$name', '$st' not found in $df")
end
return collect(df[idx, [:LON, :LAT]])
end
# Determine supplier (S) and customer (C) locations
Scity = ["Rochester", "Orlando", "San Antonio"]
Sst = ["NY", "FL", "TX"]
SXY = vcat([name2lonlat(i, j, usplace())' for (i,j) in zip(Scity, Sst)]...)
Ccity = ["Portland", "Tucson", "Denver", "Milwaukee"]
Cst = ["OR", "AZ", "CO", "WI"]
CXY = vcat([name2lonlat(i, j, usplace())' for (i,j) in zip(Ccity, Cst)]...)
4×2 Matrix{Float64}:
-122.65 45.537
-110.871 32.153
-104.881 39.7619
-87.9667 43.0633
# Data: 3 Different Products Supplied to 4 Customers
ppiTL = 131.4 # Jan 2018 (P)
ppiLTL = 179.4 # Jan 2018 (P)
tr = (r = 2ppiTL/102.7, Kwt = 25, Kcu = 2750)
fS = [500, 75, 300] # demand (ton/yr)
pct = [20, 35, 25, 20]/100 # customer demand percentage
sS = [32, 3, 12] # density (lb/ft^3)
v = [50_000, 25_000, 10_000] # product cost (in $/ton)
αS = 0 # batch production at suppliers
αC = 0.5; # constant consumption at customers
1. Direct Shipments
F = fS[:] * pct[:]' # (3 x 1) * (1 x 4) = (3 x 4) matrix
3×4 Matrix{Float64}:
100.0 175.0 125.0 100.0
15.0 26.25 18.75 15.0
60.0 105.0 75.0 60.0
S = repeat(sS[:], 1, size(CXY, 1))
3×4 Matrix{Int64}:
32 32 32 32
3 3 3 3
12 12 12 12
V = repeat(v[:], 1, size(CXY, 1))
D = Dgc(SXY, CXY) * 1.2
3×4 Matrix{Float64}:
2641.83 2346.59 1709.51 626.061
3043.26 2136.29 1867.03 1295.17
2057.81 906.461 957.05 1327.78
sh = DataFrame(f=F[:], s=S[:], α=αS+αC, v=V[:], h=0.3, d=D[:])
sh.qmax = [maxpayld(sh, tr) for sh in eachrow(sh)]
transform!(sh, AsTable(:) => ByRow(row -> minTLC(row, tr, ppiLTL)) => AsTable)
sh.t = 365.25sh.qᵒ./sh.f
sh[:, Not(:h)]
| Row | f | s | α | v | d | qmax | qᵒ | TLCᵒ | isLTL | t |
|---|---|---|---|---|---|---|---|---|---|---|
| Float64 | Int64 | Float64 | Int64 | Float64 | Float64 | Float64 | Float64 | Bool | Float64 | |
| 1 | 100.0 | 32 | 0.5 | 50000 | 2641.83 | 25.0 | 1.99905 | 1.14292e5 | true | 7.30154 |
| 2 | 15.0 | 3 | 0.5 | 25000 | 3043.26 | 4.125 | 4.125 | 43786.7 | false | 100.444 |
| 3 | 60.0 | 12 | 0.5 | 10000 | 2057.81 | 16.5 | 14.5131 | 43539.3 | false | 88.3485 |
| 4 | 175.0 | 32 | 0.5 | 50000 | 2346.59 | 25.0 | 3.10225 | 1.77379e5 | true | 6.47485 |
| 5 | 26.25 | 3 | 0.5 | 25000 | 2136.29 | 4.125 | 4.125 | 50256.0 | false | 57.3964 |
| 6 | 105.0 | 12 | 0.5 | 10000 | 906.461 | 16.5 | 12.7424 | 38227.2 | false | 44.3253 |
| 7 | 125.0 | 32 | 0.5 | 50000 | 1709.51 | 25.0 | 2.07247 | 1.17048e5 | true | 6.05576 |
| 8 | 18.75 | 3 | 0.5 | 25000 | 1867.03 | 4.125 | 4.125 | 37184.9 | false | 80.355 |
| 9 | 75.0 | 12 | 0.5 | 10000 | 957.05 | 16.5 | 11.0657 | 33197.2 | false | 53.8901 |
| 10 | 100.0 | 32 | 0.5 | 50000 | 626.061 | 25.0 | 1.2388 | 66432.0 | true | 4.52473 |
| 11 | 15.0 | 3 | 0.5 | 25000 | 1295.17 | 4.125 | 3.641 | 27307.5 | false | 88.6583 |
| 12 | 60.0 | 12 | 0.5 | 10000 | 1327.78 | 16.5 | 11.6579 | 34973.7 | false | 70.9675 |
using Statistics
out = DataFrame(Method="Direct Shipments", TLC=round(Int, sum(sh.TLCᵒ)),
t̄=365.25mean(sh.qᵒ./sh.f), LTL=sum(sh.isLTL))
| Row | Method | TLC | t̄ | LTL |
|---|---|---|---|---|
| String | Int64 | Float64 | Int64 | |
| 1 | Direct Shipments | 783623 | 50.7285 | 4 |
2. Uncoordinated: Use a DC located at Memphis, TN, with no coordination
DCcity = "Memphis"
DCXY = name2lonlat(DCcity, "TN", usplace())'
shS = DataFrame(f=fS, s=sS, α=αS+0.5, v=v)
shC = DataFrame(f=sum(shS.f)*pct,
s=sum(shS.f)./sum(shS.f./shS.s),
α=αC,
v=sum(shS.f.*shS.v)/sum(shS.f))
sh = vcat(shS, shC)
sh.h .= 0.3
sh.d = Dgc(DCXY, [SXY; CXY])[:] * 1.2
sh.qmax = [maxpayld(sh, tr) for sh in eachrow(sh)]
transform!(sh, AsTable(:) => ByRow(row -> minTLC(row, tr)) => AsTable)
sh.t = 365.25sh.qᵒ./sh.f
sh[:, Not(:h)]
| Row | f | s | α | v | d | qmax | qᵒ | TLCᵒ | isLTL | t |
|---|---|---|---|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Bool | Float64 | |
| 1 | 500.0 | 32.0 | 0.5 | 50000.0 | 1036.01 | 25.0 | 13.2942 | 1.99414e5 | false | 9.71144 |
| 2 | 75.0 | 3.0 | 0.5 | 25000.0 | 827.459 | 4.125 | 4.125 | 53966.8 | false | 20.0888 |
| 3 | 300.0 | 12.0 | 0.5 | 10000.0 | 760.314 | 16.5 | 16.5 | 60124.1 | false | 20.0888 |
| 4 | 175.0 | 13.3333 | 0.5 | 34142.9 | 2220.23 | 18.3333 | 13.9332 | 1.42715e5 | false | 29.0805 |
| 5 | 306.25 | 13.3333 | 0.5 | 34142.9 | 1460.96 | 18.3333 | 14.9516 | 1.53148e5 | false | 17.8321 |
| 6 | 218.75 | 13.3333 | 0.5 | 34142.9 | 1053.32 | 18.3333 | 10.7296 | 1.09902e5 | false | 17.9154 |
| 7 | 175.0 | 13.3333 | 0.5 | 34142.9 | 671.89 | 18.3333 | 7.66478 | 78509.3 | false | 15.9975 |
out = vcat(out, DataFrame(Method="Uncoord Inv at DC", TLC=round(Int, sum(sh.TLCᵒ)),
t̄=365.25mean(sh.qᵒ./sh.f), LTL=sum(sh.isLTL)))
| Row | Method | TLC | t̄ | LTL |
|---|---|---|---|---|
| String | Int64 | Float64 | Int64 | |
| 1 | Direct Shipments | 783623 | 50.7285 | 4 |
| 2 | Uncoord Inv at DC | 797779 | 18.6735 | 0 |
3. Cross-Docking: Use a DC located at Memphis, TN, with perfect cross-docking
shXD = sh[:, Not([:qᵒ, :TLCᵒ, :isLTL, :t])]
shXD[idxin, :α] .= αS # Outbound α is unchanged
TLC_XDh(t) = sum(sh -> totlogcost(sh.f*t[1], tranchgTL(sh.f*t[1], sh, tr), sh),
eachrow(shXD))
t₀ = maximum(shXD.qmax./shXD.f)
tᵒ = optimize(TLC_XDh, 0, t₀).minimizer[1] # 1-D bounded search (univariate opt)
shXD.qXD = [sh.f*tᵒ for sh in eachrow(shXD)]
shXD.TLC_XD = [totlogcost(sh.f*tᵒ, tranchgTL(sh.f*tᵒ, sh, tr), sh)
for sh in eachrow(shXD)]
shXD.t = 365.25shXD.qXD./shXD.f
shXD[:, Not(:h)]
| Row | f | s | α | v | d | qmax | qXD | TLC_XD | t |
|---|---|---|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 500.0 | 32.0 | 0.0 | 50000.0 | 1036.01 | 25.0 | 25.0 | 53021.0 | 18.2625 |
| 2 | 75.0 | 3.0 | 0.0 | 25000.0 | 827.459 | 4.125 | 3.75 | 42347.8 | 18.2625 |
| 3 | 300.0 | 12.0 | 0.5 | 10000.0 | 760.314 | 16.5 | 15.0 | 61411.5 | 18.2625 |
| 4 | 175.0 | 13.3333 | 0.5 | 34142.9 | 2220.23 | 18.3333 | 8.75 | 1.5844e5 | 18.2625 |
| 5 | 306.25 | 13.3333 | 0.5 | 34142.9 | 1460.96 | 18.3333 | 15.3125 | 1.53191e5 | 18.2625 |
| 6 | 218.75 | 13.3333 | 0.5 | 34142.9 | 1053.32 | 18.3333 | 10.9375 | 1.09922e5 | 18.2625 |
| 7 | 175.0 | 13.3333 | 0.5 | 34142.9 | 671.89 | 18.3333 | 8.75 | 79198.6 | 18.2625 |
out = vcat(out, DataFrame(Method="Cross-Docking at DC",
TLC=round(Int, sum(shXD.TLC_XD)), t̄=mean(shXD.t), LTL=0))
| Row | Method | TLC | t̄ | LTL |
|---|---|---|---|---|
| String | Int64 | Float64 | Int64 | |
| 1 | Direct Shipments | 783623 | 50.7285 | 4 |
| 2 | Uncoord Inv at DC | 797779 | 18.6735 | 0 |
| 3 | Cross-Docking at DC | 657532 | 18.2625 | 0 |
using GeoMakie, Logjam.MapTools
XY = vcat(SXY, DCXY, CXY)
fig, ax = makemap(eachcol(XY)..., xexpand=0.25, yexpand=0.25)
h = []
push!(h, scatter!(ax, eachcol(SXY)...,
marker=:circle, color=:red, label="Suppliers"))
push!(h, scatter!(ax, eachcol(DCXY)...,
marker=:utriangle, markersize=14, label="DC"))
push!(h, scatter!(ax, eachcol(CXY)..., marker=:rect, label="Customers"))
text!(ax, eachcol(vcat(SXY, DCXY, CXY))...,
text=vcat(Scity, DCcity, Ccity); aligntext(eachcol(vcat(SXY, DCXY, CXY))...)...)
Legend(fig[1,2], h, [x.label for x in h])
fig