A logistics company needs to optimize the delivery of three types of drinks (Cola, Juice, and Mineral Water) from two production facilities (P1 and P2) to three retail stores (S1, S2, S3) via a transportation network with 5 intermediate nodes (N1-N5). The directed arcs between nodes have capacity limits (in pallets) and unit transportation costs ($/pallet).

The network connections, capacities, and costs are as follows:
- P1→N1 (Capacity: 80, Cost: 2)
- P1→N2 (Capacity: 60, Cost: 3)
- P2→N3 (Capacity: 90, Cost: 2)
- N1→N3 (Capacity: 50, Cost: 1)
- N2→N4 (Capacity: 70, Cost: 2)
- N3→N5 (Capacity: 100, Cost: 3)
- N4→S1 (Capacity: ∞, Cost: 1)
- N4→S2 (Capacity: ∞, Cost: 2)
- N5→S2 (Capacity: ∞, Cost: 1)
- N5→S3 (Capacity: ∞, Cost: 2)

The demand requirements are: Cola (S1:30, S2:20, S3:10), Juice (S1:15, S2:25, S3:5), Water (S1:10, S2:15, S3:20). Production capacities are P1 (Cola:40, Juice:30, Water:20) and P2 (Cola:30, Juice:25, Water:40). The goal is to satisfy all demands while minimizing total transportation costs.

Problem type: LP (Medium: 30 variables, 36 constraints, 78 nonzeros)

Mathematical model:
Set:
1. Nodes
The set of all nodes \( V = \{P1, P2, N1, N2, N3, N4, N5, S1, S2, S3\} \)
2. Arcs
The set of directed arcs \( A \), defined by the connections in problem description
3. Commodities
The set of beverage types \( K = \{Cola, Juice, Water\} \)

Parameter:
1. Capacity
\( u_{ij} \): Arc capacity from node i to j (values given in problem description, with N3->N5 capacity corrected to 100)
2. Cost
\( c_{ij} \): Unit transportation cost on arc (i,j).
3. Demand
\( d_j^k \): Demand for commodity k at store j (values given)
4. Supply
\( s_i^k \): Maximum supply capacity of commodity k at production facility i (values given)

Decision variable:
1. Flow
\( x_{ij}^k \geq 0 \): Flow amount of commodity k on arc (i,j)

Objective:
Minimize total transportation cost
min: \( \sum_{k\in K} \sum_{(i,j)\in A} c_{ij} x_{ij}^k \)

Constraint:
1. Flow conservation
   Net outflow from production nodes must be less than or equal to supply capacity.
   \( \sum_{j:(i,j)\in A} x_{ij}^k - \sum_{j:(j,i)\in A} x_{ji}^k \leq s_i^k \) for production nodes
   \( \sum_{j:(i,j)\in A} x_{ij}^k - \sum_{j:(j,i)\in A} x_{ji}^k = -d_i^k \) for store nodes (demand must be met exactly)
   \( \sum_{j:(i,j)\in A} x_{ij}^k - \sum_{j:(j,i)\in A} x_{ji}^k = 0 \) for intermediate nodes
2. Capacity constraint
   \( \sum_{k\in K} x_{ij}^k \leq u_{ij} \) for all (i,j) ∈ A
3. Non-negativity
   \( x_{ij}^k \geq 0 \) for all (i,j) ∈ A, k ∈ K

Type:
Continuous, Linear, Linear
LP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_beverage_distribution(
    nodes=['P1', 'P2', 'N1', 'N2', 'N3', 'N4', 'N5', 'S1', 'S2', 'S3'],
    commodities=['Cola', 'Juice', 'Water'],
    arcs_data=[
        ('P1', 'N1', 80, 2), ('P1', 'N2', 60, 3), ('P2', 'N3', 90, 2),
        ('N1', 'N3', 50, 1), ('N2', 'N4', 70, 2), ('N3', 'N5', 100, 3),
        ('N4', 'S1', float('inf'), 1), ('N4', 'S2', float('inf'), 2),
        ('N5', 'S2', float('inf'), 1), ('N5', 'S3', float('inf'), 2)
    ],
    demand={
        'S1': {'Cola': 30, 'Juice': 15, 'Water': 10},
        'S2': {'Cola': 20, 'Juice': 25, 'Water': 15},
        'S3': {'Cola': 10, 'Juice': 5, 'Water': 20}
    },
    supply={
        'P1': {'Cola': 40, 'Juice': 30, 'Water': 20},
        'P2': {'Cola': 30, 'Juice': 25, 'Water': 40}
    }
):
    """Solve the beverage distribution optimization problem using Gurobi."""
    model = gp.Model("BeverageDistribution")

    arcs, capacity, cost = gp.multidict({
        (i, j): [cap, c] for (i, j, cap, c) in arcs_data
    })

    flow = model.addVars(arcs, commodities, lb=0, name="flow")

    model.setObjective(
        gp.quicksum(cost[i, j] * flow[i, j, k] for i, j in arcs for k in commodities),
        GRB.MINIMIZE
    )

    production_nodes = supply.keys()
    demand_nodes = demand.keys()

    for node in nodes:
        for k in commodities:
            net_flow = (flow.sum(node, '*', k) - flow.sum('*', node, k))
            if node in production_nodes:
                model.addConstr(net_flow <= supply[node].get(k, 0))
            elif node in demand_nodes:
                model.addConstr(net_flow == -demand[node].get(k, 0))
            else:
                model.addConstr(net_flow == 0)

    for i, j in arcs:
        if capacity[i, j] != float('inf'):
            model.addConstr(gp.quicksum(flow[i, j, k] for k in commodities) <= capacity[i, j])

    model.optimize()

    if model.status == GRB.OPTIMAL:
        return {"status": "optimal", "obj": model.ObjVal}
    else:
        return {"status": f"{model.status}"}


if __name__ == "__main__":
    result = solve_beverage_distribution()
    print(result)


Solution status: OPTIMAL
Optimal objective value: 940