A logistics company needs to pack 12 rectangular parcels into a shipping container with fixed width of 100 cm and variable height. The parcels have different dimensions and must be placed orthogonally (no rotations allowed). The goal is to arrange all parcels within the container while minimizing the total height used. The parcels have the following dimensions (width × height in cm):
1. 30×40, 2. 25×35, 3. 40×20, 4. 15×25, 5. 35×30, 6. 20×15, 7. 25×20, 8. 30×25, 9. 15×10, 10. 20×30, 11. 10×15, 12. 25×10. Parcels cannot overlap and must be completely contained within the container's width. The container has unlimited height capacity, but the objective is to minimize the used height.

Problem type: MILP (Medium: 313 variables, 354 constraints, 1092 nonzeros)

Mathematical model:
Set:
1. Parcels
The set of parcels, \( P = \{1, 2, ..., 12\} \)

Parameter:
1. ContainerWidth
100 (cm)
2. ParcelWidths
[30, 25, 40, 15, 35, 20, 25, 30, 15, 20, 10, 25] (cm)
3. ParcelHeights
[40, 35, 20, 25, 30, 15, 20, 25, 10, 30, 15, 10] (cm)
4. M (Large constant)
1000 (sufficiently large number for big-M constraints)

Decision variable:
1. x_p
Continuous variable \( x_p \geq 0 \) for all \( p \in P \), x-coordinate of bottom-left corner of parcel p
2. y_p
Continuous variable \( y_p \geq 0 \) for all \( p \in P \), y-coordinate of bottom-left corner of parcel p
3. a_{pq}
Binary variable for all \( p,q \in P, p < q \), indicating relative positioning:
   - \( a_{pq} = 1 \) if parcel p is to the left of q
   - \( a_{pq} = 0 \) otherwise
4. b_{pq}
Binary variable for all \( p,q \in P, p < q \), indicating relative positioning:
   - \( b_{pq} = 1 \) if parcel p is below q
   - \( b_{pq} = 0 \) otherwise
5. H
Continuous variable \( H \geq 0 \), representing total container height

Objective:
Minimize the total height of the container
min: H

Constraint:
1. Parcels must fit within container width
\( x_p + \text{ParcelWidths}_p \leq \text{ContainerWidth}, \forall p \in P \)
2. Parcels must fit within container height
\( y_p + \text{ParcelHeights}_p \leq H, \forall p \in P \)
3. Non-overlapping constraints (for all \( p,q \in P, p < q \)):
   \( x_p + \text{ParcelWidths}_p \leq x_q + M(1-a_{pq}) \)
   \( x_q + \text{ParcelWidths}_q \leq x_p + M(1-a_{qp}) \)
   \( y_p + \text{ParcelHeights}_p \leq y_q + M(1-b_{pq}) \)
   \( y_q + \text{ParcelHeights}_q \leq y_p + M(1-b_{qp}) \)
   \( a_{pq} + a_{qp} + b_{pq} + b_{qp} \geq 1 \)
4. Height calculation
\( H \geq y_p + \text{ParcelHeights}_p, \forall p \in P \)

Type:
Mixed, Linear, Linear
MILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_container_packing(
    parcels=list(range(12)),
    parcel_widths=[30, 25, 40, 15, 35, 20, 25, 30, 15, 20, 10, 25],
    parcel_heights=[40, 35, 20, 25, 30, 15, 20, 25, 10, 30, 15, 10],
    container_width=100,
    M=1000
):
    """Solve the rectangular packing problem to minimize container height."""
    model = gp.Model("ContainerPacking")

    # Create variables
    x = model.addVars(parcels, lb=0, name="x")
    y = model.addVars(parcels, lb=0, name="y")
    a = model.addVars(parcels, parcels, vtype=GRB.BINARY, name="a")
    b = model.addVars(parcels, parcels, vtype=GRB.BINARY, name="b")
    H = model.addVar(lb=0, name="H")

    # Set objective
    model.setObjective(H, GRB.MINIMIZE)

    # Add constraints
    model.addConstrs(
        (x[p] + parcel_widths[p] <= container_width for p in parcels),
        name="width_constraint")

    for p in parcels:
        for q in parcels:
            if p < q:
                model.addConstr(x[p] + parcel_widths[p] <= x[q] + M * (1 - a[p, q]))
                model.addConstr(x[q] + parcel_widths[q] <= x[p] + M * (1 - a[q, p]))
                model.addConstr(y[p] + parcel_heights[p] <= y[q] + M * (1 - b[p, q]))
                model.addConstr(y[q] + parcel_heights[q] <= y[p] + M * (1 - b[q, p]))
                model.addConstr(a[p, q] + a[q, p] + b[p, q] + b[q, p] >= 1)

    model.addConstrs((H >= y[p] + parcel_heights[p] for p in parcels),
                     name="height_calculation")

    # Optimize model
    model.optimize()

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


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


Solution status: OPTIMAL
Optimal objective value: 75