A regional beverage distributor needs to optimize the delivery of 5 products (A,B,C,D,E) from their central warehouse (Node 1) to 3 retail stores (Nodes 4,5,6) through a transportation network with 6 nodes and 8 directed arcs. Each arc has a maximum capacity (in pallets/day) and transportation cost ($/pallet): (1→2): 20 pallets, $3; (1→3): 15 pallets, $4; (2→3): 10 pallets, $2; (2→4): 12 pallets, $5; (3→5): 18 pallets, $3; (3→6): 10 pallets, $6; (4→5): 8 pallets, $1; (5→6): 15 pallets, $2. The daily demands are: Store 4 needs 8 pallets (A:3, B:2, C:3), Store 5 needs 12 pallets (B:4, D:5, E:3), Store 6 needs 10 pallets (A:2, C:3, E:5). The warehouse can supply up to 30 pallets total daily. The goal is to satisfy all demands at minimum transportation cost while respecting arc capacities and product-specific requirements.

Problem type: LP (Medium: 40 variables, 34 constraints, 120 nonzeros)

Mathematical model:
Set:
1. Nodes
The set of nodes in the network, \( N = \{1,2,3,4,5,6\} \), where 1 is the source, {4,5,6} are sinks
2. Arcs
The set of directed arcs, \( A = \{(1,2),(1,3),(2,3),(2,4),(3,5),(3,6),(4,5),(5,6)\} \)
3. Products
The set of products, \( P = \{A,B,C,D,E\} \)

Parameter:
1. ArcCapacity
The maximum flow capacity for each arc (pallets/day):
\( u_{12} = 20, u_{13} = 15, u_{23} = 10, u_{24} = 12, u_{35} = 18, u_{36} = 10, u_{45} = 8, u_{56} = 15 \)
2. ArcCost
The transportation cost per pallet for each arc ($):
\( c_{12} = 3, c_{13} = 4, c_{23} = 2, c_{24} = 5, c_{35} = 3, c_{36} = 6, c_{45} = 1, c_{56} = 2 \)
3. Demand
The product-specific demand at each sink node (pallets/day):
\( d_{4A} = 3, d_{4B} = 2, d_{4C} = 3, d_{5B} = 4, d_{5D} = 5, d_{5E} = 3, d_{6A} = 2, d_{6C} = 3, d_{6E} = 5 \)
4. SupplyLimit
The maximum total supply from the source: 30 pallets/day

Decision variable:
1. ProductFlow
\( x_{ij}^p \geq 0 \) for all \( (i,j) \in A, p \in P \), representing the flow of product \( p \) on arc \( (i,j) \)

Objective:
Minimize the total transportation cost
min: \( \sum_{(i,j)\in A} \sum_{p\in P} c_{ij} \cdot x_{ij}^p \)

Constraint:
1. Flow conservation at intermediate nodes
\( \sum_{j:(i,j)\in A} x_{ij}^p - \sum_{j:(j,i)\in A} x_{ji}^p = 0, \forall i \in N\setminus\{1,4,5,6\}, \forall p \in P \)
2. Demand satisfaction at sink nodes
\( \sum_{j:(j,i)\in A} x_{ji}^p - \sum_{j:(i,j)\in A} x_{ij}^p = d_{ip}, \forall i \in \{4,5,6\}, \forall p \in P \)
3. Arc capacity constraints
\( \sum_{p\in P} x_{ij}^p \leq u_{ij}, \forall (i,j) \in A \)
4. Source supply limit
\( \sum_{j:(1,j)\in A} \sum_{p\in P} x_{1j}^p \leq 30 \)

Type:
Continuous, Linear, Linear
LP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_beverage_distribution(
    N=[1, 2, 3, 4, 5, 6],
    A=[(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (3, 6), (4, 5), (5, 6)],
    P=['A', 'B', 'C', 'D', 'E'],
    source=1,
    sinks=[4, 5, 6],
    u={
        (1, 2): 20, (1, 3): 15, (2, 3): 10, (2, 4): 12,
        (3, 5): 18, (3, 6): 10, (4, 5): 8, (5, 6): 15
    },
    c={
        (1, 2): 3, (1, 3): 4, (2, 3): 2, (2, 4): 5,
        (3, 5): 3, (3, 6): 6, (4, 5): 1, (5, 6): 2
    },
    d={
        (4, 'A'): 3, (4, 'B'): 2, (4, 'C'): 3,
        (5, 'B'): 4, (5, 'D'): 5, (5, 'E'): 3,
        (6, 'A'): 2, (6, 'C'): 3, (6, 'E'): 5
    },
    supply_limit=30
):
    """Solve the multi-commodity beverage distribution problem using Gurobi."""
    # Create model
    model = gp.Model("BeverageDistribution")

    # ========== Decision Variables ==========
    # Flow of product p on arc (i,j)
    x = model.addVars(A, P, lb=0, name="flow")

    # ========== Objective Function ==========
    # Minimize total transportation cost
    model.setObjective(
        gp.quicksum(c[i, j] * x[i, j, p] for (i, j) in A for p in P),
        GRB.MINIMIZE
    )

    # ========== Constraints ==========
    # 1. Flow conservation at intermediate nodes (nodes 2,3)
    for i in [2, 3]:
        for p in P:
            model.addConstr(
                gp.quicksum(x[i, j, p] for (i_, j) in A if i_ == i) -
                gp.quicksum(x[j, i, p] for (j, i_) in A if i_ == i) == 0,
                f"flow_conservation_node_{i}_product_{p}"
            )

    # 2. Demand satisfaction at sink nodes
    for i in sinks:
        for p in P:
            if (i, p) in d:
                model.addConstr(
                    gp.quicksum(x[j, i, p] for (j, i_) in A if i_ == i) -
                    gp.quicksum(x[i, j, p] for (i_, j) in A if i_ == i) == d[i, p],
                    f"demand_satisfaction_node_{i}_product_{p}"
                )
            else:
                # No demand for this product at this node
                model.addConstr(
                    gp.quicksum(x[j, i, p] for (j, i_) in A if i_ == i) -
                    gp.quicksum(x[i, j, p] for (i_, j) in A if i_ == i) == 0,
                    f"no_demand_node_{i}_product_{p}"
                )

    # 3. Arc capacity constraints
    for (i, j) in A:
        model.addConstr(
            gp.quicksum(x[i, j, p] for p in P) <= u[i, j],
            f"arc_capacity_{i}_{j}"
        )

    # 4. Source supply limit
    model.addConstr(
        gp.quicksum(x[source, j, p] for (i, j) in A if i == source for p in P) <= supply_limit,
        "source_supply_limit"
    )

    # ========== Solve the Model ==========
    model.optimize()

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


# Execute the function
if __name__ == "__main__":
    result = solve_beverage_distribution()
    print(result)

Solution status: OPTIMAL
Optimal objective value: 249