A warehouse needs to distribute 5 types of products (A, B, C, D, E) to 3 retail stores (Store1, Store2, Store3) using delivery trucks. Each truck has a maximum capacity of 15 tons. The products have the following weights per unit: A=2 tons, B=3 tons, C=1 ton, D=4 tons, E=2 tons. The stores require the following quantities: Store1 needs 2A+1B+3C, Store2 needs 1A+2D+1E, Store3 needs 3B+2C+1E. The warehouse has 4 trucks available. The goal is to minimize the total number of trucks used while meeting all store demands without exceeding truck capacities.

Problem type: ILP (Medium: 64 variables, 79 constraints, 244 nonzeros)

Mathematical model:
Mathematical Model:
Set:
1. Products
The set of products, \( P = \{A, B, C, D, E\} \)
2. Stores
The set of stores, \( S = \{Store1, Store2, Store3\} \)
3. Trucks
The set of trucks, \( T = \{1, 2, 3, 4\} \)

Parameter:
1. ProductWeights
[2, 3, 1, 4, 2] (tons) for products A to E respectively
2. StoreDemands
- Store1: [2, 1, 3, 0, 0] (units of A-E)
- Store2: [1, 0, 0, 2, 1] 
- Store3: [0, 3, 2, 0, 1]
3. TruckCapacity
15 (tons)

Decision variable:
1. ProductDelivery
Integer variable \( x_{pst} \geq 0 \) for all \( p \in P, s \in S, t \in T \), representing quantity of product p delivered to store s using truck t
2. TruckUsed
Binary variable \( y_t \in \{0, 1\} \) for all \( t \in T \), where \( y_t = 1 \) if truck t is used

Objective:
Minimize total trucks used
min: \sum_{t\in T}y_t

Constraint:
1. Meet all store demands
\sum_{t\in T}x_{pst} = StoreDemands_{sp}, \forall p \in P, s \in S
2. Truck capacity limit
\sum_{p\in P}\sum_{s\in S}ProductWeights_p \cdot x_{pst} \leq TruckCapacity \cdot y_t, \forall t \in T
3. Logical constraint (if any product is delivered by truck t, it must be marked as used)
x_{pst} \leq M \cdot y_t, \forall p \in P, s \in S, t \in T (where M is a large enough constant)

Type:
Integer, Linear, Linear
ILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB

def solve_warehouse_distribution(
    products=['A', 'B', 'C', 'D', 'E'],
    stores=['Store1', 'Store2', 'Store3'],
    trucks=[1, 2, 3, 4],
    product_weights={'A': 2, 'B': 3, 'C': 1, 'D': 4, 'E': 2},
    truck_capacity=15,
    store_demands={
        'Store1': {'A': 2, 'B': 1, 'C': 3, 'D': 0, 'E': 0},
        'Store2': {'A': 1, 'B': 0, 'C': 0, 'D': 2, 'E': 1},
        'Store3': {'A': 0, 'B': 3, 'C': 2, 'D': 0, 'E': 1}
    }
):
    """
    Solves the warehouse distribution problem using Gurobi.
    Minimizes the number of trucks used to deliver products to stores while meeting demand and capacity constraints.
    """
    # Create model
    model = gp.Model("WarehouseDistribution")

    # Decision variables
    # x[p,s,t]: quantity of product p delivered to store s using truck t
    x = model.addVars(products, stores, trucks, vtype=GRB.INTEGER, name="ProductDelivery")

    # y[t]: binary variable indicating if truck t is used
    y = model.addVars(trucks, vtype=GRB.BINARY, name="TruckUsed")

    # Objective: Minimize total trucks used
    model.setObjective(y.sum(), GRB.MINIMIZE)

    # Constraints
    # 1. Meet all store demands
    for s in stores:
        for p in products:
            model.addConstr(
                gp.quicksum(x[p, s, t] for t in trucks) == store_demands[s][p],
                name=f"Demand_{s}_{p}"
            )

    # 2. Truck capacity limit
    for t in trucks:
        model.addConstr(
            gp.quicksum(product_weights[p] * x[p, s, t] for p in products for s in stores)
            <= truck_capacity * y[t],
            name=f"Capacity_{t}"
        )

    # 3. Logical constraint: if any product is delivered by truck t, y_t must be 1
    # Using a large M (here M = max possible demand for any product)
    M = max(max(demands.values()) for demands in store_demands.values())
    for p in products:
        for s in stores:
            for t in trucks:
                model.addConstr(
                    x[p, s, t] <= M * y[t],
                    name=f"Logic_{p}_{s}_{t}"
                )

    # Solve the model
    model.optimize()

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


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

Solution status: OPTIMAL
Optimal objective value: 3