A regional beverage distributor needs to design an optimal delivery network from their central warehouse (node 0) to 8 retail stores (nodes 1-8). The road network between locations has the following distances (in km): [(0,1)=12, (0,2)=8, (1,2)=5, (1,3)=9, (2,4)=7, (3,4)=6, (3,5)=11, (4,6)=10, (5,6)=4, (5,7)=8, (6,8)=7, (7,8)=6]. Each delivery truck has a capacity of serving maximum 3 stores (including transfers). The company wants to minimize total travel distance while ensuring no truck serves more than 3 nodes in its subtree. The warehouse must be connected to all stores through a single network without cycles.

Problem type: MILP (Medium: 48 variables, 54 constraints, 180 nonzeros)

Mathematical model:
Set
N: The set of all locations (nodes), where 0 is the warehouse.
E: The set of all possible connections (edges) between nodes.

Parameter
d_ij: The distance of the edge between node i and j.
C: The capacity of a truck (max number of stores in a subtree).
q_i: The demand at each node (1 for each store, 0 for the warehouse).

Decision variable
x_ij: Binary, 1 if the edge (i,j) is selected for the tree.
f_ij: Continuous, represents the amount of "flow" on the edge from i to j.

Objective
# Minimize the total distance of the selected edges.
min: ( \sum_{(i,j) \in E} d_{ij} \cdot x_{ij} )

Constraint
1. Spanning Tree Structure: Exactly |N|-1 edges must be selected.
   \sum_{(i,j) \in E} x_{ij} = |N| - 1
2. Flow Balance: For each node, the flow coming in must equal the flow going out plus that node's own demand. This ensures connectivity and prevents cycles.
   \sum_{j:(j,i) \in E} f_{ji} - \sum_{j:(i,j) \in E} f_{ij} = q_i,  \forall i \in N
3. Flow-Edge Link: Flow can only pass through an edge if that edge is selected. The flow represents the number of nodes being served, so its maximum value is |N|-1.
   f_{ij} <= (|N| - 1) \cdot x_{ij},  \forall (i,j) \in E
   f_{ji} <= (|N| - 1) \cdot x_{ji},  \forall (i,j) \in E
4. Capacity Constraint: The flow on any edge cannot exceed the truck's capacity. This effectively limits the size of the subtree.
   f_{ij} + f_{ji} \leq C \cdot x_{ij},  \forall (i,j) \in E, i,j \neq 0

Type:
Mixed, Linear, Linear
MILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB
import itertools


def solve_capacitated_mst(
    nodes=list(range(9)),
    warehouse=0,
    distances_data=None,
    capacity=3,
    time_limit=60
):
    """
    Solves the capacitated minimal spanning tree problem using a
    single-commodity flow formulation.
    """
    if distances_data is None:
        distances = {
            (0, 1): 12, (0, 2): 8, (1, 2): 5, (1, 3): 9, (2, 4): 7, (3, 4): 6,
            (3, 5): 11, (4, 6): 10, (5, 6): 4, (5, 7): 8, (6, 8): 7, (7, 8): 6
        }
        for i, j in distances.copy():
            distances[j, i] = distances[i, j]
    else:
        distances = distances_data

    stores = [n for n in nodes if n != warehouse]
    demands = {n: 1 if n in stores else -(len(stores)) for n in nodes}

    model = gp.Model("Corrected_CMST")

    x = model.addVars(distances.keys(), vtype=GRB.BINARY, name="edge_selected")
    f = model.addVars(distances.keys(), vtype=GRB.CONTINUOUS, name="flow")

    model.setObjective(
        gp.quicksum(distances[i, j] * x[i, j] for i, j in distances if i < j),
        GRB.MINIMIZE
    )

    model.addConstr(x.sum('*', '*') / 2 == len(nodes) - 1, "num_edges")

    for i in nodes:
        in_flow = gp.quicksum(f[j, i] for j in nodes if (j, i) in distances)
        out_flow = gp.quicksum(f[i, j] for j in nodes if (i, j) in distances)
        model.addConstr(in_flow - out_flow == demands[i], f"flow_balance_{i}")

    for i, j in distances:
        model.addConstr(f[i, j] <= (len(nodes) - 1) * x[i, j], f"flow_link_{i}_{j}")

    for i, j in distances:
        if i != warehouse and j != warehouse:
            model.addConstr(f[i, j] + f[j, i] <= capacity * x[i, j], f"capacity_{i}_{j}")

    model.Params.TimeLimit = time_limit
    model.optimize()

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


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


Solution status: OPTIMAL
Optimal objective value: 72