# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # The cannery problem

# **This tutorial was originally contributed by Louis Luangkesorn.**

# This tutorial solves the cannery problem—a classic transshipment problem
# from Dantzig's _Linear Programming and Extensions_—using JSON data to supply
# the model parameters. It demonstrates how to build a JuMP model from
# structured external data.
#
# **Learning intentions:**
# * Formulate a classic transportation problem as a linear program with per-plant
#   supply limits, per-market demand requirements, and a cost-minimising objective
# * Load problem data from a JSON source and derive the index sets and parameters
#   that drive the JuMP model, rather than hard-coding them

# ## Required packages

# This tutorial uses the following packages:

using JuMP
import HiGHS
import JSON
import Test

# ## Formulation

# The cannery problem assumes we are optimizing the shipment of cases of
# cans from production plants ``p \in P`` to markets ``m \in M``.

# Each production plant ``p`` has a capacity ``c_p``, and each market ``m``
# has a demand ``d_m``. The shipping cost per case of cans from plant ``p``
# to market ``m`` is ``d_{p,m}``.

# We wish to find the distribution plan ``x_{p,m}``, the number of cases of cans
# to ship from plant ``p`` to market ``m``, for ``p \in P`` and ``m \in M``
# that minimizes the shipping costs. We can formulate our problem as the
# following linear program:
# ```math
# \begin{aligned}
# \min & \sum\limits_{p \in P}\sum\limits_{m \in M} d_{p,m} x_{p,m} \\
# \text{s.t.} & \sum\limits_{m \in M} x_{p,m} \le c_p, && \forall p \in P \\
#             & \sum\limits_{p \in P} x_{p,m} \ge d_m, && \forall m \in M \\
#             & x_{p,m} \ge 0, && \forall p \in P, m \in M
# \end{aligned}
# ```

# ## Data

# A key feature of the tutorial is to demonstrate how to load data from JSON.

# For simplicity, we've hard-coded it below. But if the data was available as a
# `.json` file, we could use `data = JSON.parsefile(filename)` to read in the
# data.

data = JSON.parse("""
{
    "plants": {
        "Seattle": {"capacity": 350},
        "San-Diego": {"capacity": 600}
    },
    "markets": {
        "New-York": {"demand": 300},
        "Chicago": {"demand": 300},
        "Topeka": {"demand": 300}
    },
    "distances": {
        "Seattle => New-York": 2.5,
        "Seattle => Chicago": 1.7,
        "Seattle => Topeka": 1.8,
        "San-Diego => New-York": 2.5,
        "San-Diego => Chicago": 1.8,
        "San-Diego => Topeka": 1.4
    }
}
""")

# Create the set of plants:

P = keys(data["plants"])

# Create the set of markets:

M = keys(data["markets"])

# We also need a function to compute the distance from plant to market:

distance(p::String, m::String) = data["distances"]["$(p) => $(m)"]

# ## JuMP formulation

# Now we're ready to convert our mathematical formulation into a JuMP model.

# First, create a new JuMP model. Since we have a linear program, we'll use
# HiGHS as our optimizer:

model = Model(HiGHS.Optimizer)

# Our decision variables are indexed over the set of plants and markets:

@variable(model, x[P, M] >= 0)

# We need a constraint that each plant can ship no more than its capacity:

@constraint(model, [p in P], sum(x[p, :]) <= data["plants"][p]["capacity"])

# and each market must receive at least its demand:

@constraint(model, [m in M], sum(x[:, m]) >= data["markets"][m]["demand"])

# Finally, our objective is to minimize the transportation distance:

@objective(model, Min, sum(distance(p, m) * x[p, m] for p in P, m in M));

# ## Solution

# Let's optimize and look at the solution:

optimize!(model)
solution_summary(model)

# What's the optimal shipment?

assert_is_solved_and_feasible(model)
Test.@test isapprox(objective_value(model), 1_680.0, atol = 1e-6)
for p in P, m in M
    println(p, " => ", m, ": ", value(x[p, m]))
end
