A logistics company needs to pack 6 heavy cylindrical containers into a specialized truck bed. The truck bed has a fixed width of 2.5 meters, and the company wants to find the minimum possible length required to fit all containers. The diameters of the 6 containers (in meters) are: [1.2, 1.1, 1.0, 0.9, 0.8, 0.7].

The containers must be placed flat on the truck bed according to the following rules:
1.  Each container must be fully contained within the truck bed's boundaries.
2.  No two containers can overlap.
3.  The goal is to find an arrangement that minimizes the total length of the truck bed used.

The position of each container is determined by the (x, y) coordinates of its center. The x-axis runs along the length of the truck, and the y-axis runs along its fixed width.

Problem type: NLP (Small: 13 variables, 24 constraints, 30 nonzeros)

Mathematical model:
# Set
# C: The set of containers to be packed.
Set:
1. Containers
The set of containers, \( C = \{1, 2, 3, 4, 5, 6\} \)

# Parameter
# TruckWidth: The fixed width of the truck bed in meters.
# Diameter_c: The diameter of container c.
# Radius_c: The radius of container c, calculated as Diameter_c / 2.
Parameter:
1. TruckWidth
2.5 (meters)
2. Diameters
The diameter of each container \( c \in C \) (meters): [1.2, 1.1, 1.0, 0.9, 0.8, 0.7]
3. Radii
The radius of each container \( c \in C \) (meters): [0.6, 0.55, 0.5, 0.45, 0.4, 0.35]

# Decision variable
# x_c, y_c: The continuous variables representing the center coordinates of container c.
# L: A continuous variable representing the required length of the truck bed.
Decision variable:
1. x_c
Continuous variable \( x_c \geq 0 \) for all \( c \in C \), representing the x-coordinate (length dimension) of container \( c \)'s center.
2. y_c
Continuous variable \( y_c \geq 0 \) for all \( c \in C \), representing the y-coordinate (width dimension) of container \( c \)'s center.
3. L
Continuous variable \( L \geq 0 \), representing the required length of the truck bed.

# Objective
# Minimize the required length of the truck bed.
Objective:
Minimize the truck length
min: \( L \)

# Constraint
# 1. Containers must fit within the truck width (respecting boundaries).
# 2. Containers must fit within the required truck length (respecting boundaries).
# 3. No two containers i and j can overlap. This is enforced by ensuring the distance between
#    their centers is at least the sum of their radii.
Constraint:
1. Width Boundary
\( y_c + \text{Radius}_c \leq \text{TruckWidth}, \quad \forall c \in C \)
\( y_c \geq \text{Radius}_c, \quad \forall c \in C \)
2. Length Boundary
\( x_c + \text{Radius}_c \leq L, \quad \forall c \in C \)
\( x_c \geq \text{Radius}_c, \quad \forall c \in C \)
3. Non-Overlapping
\( (x_i - x_j)^2 + (y_i - y_j)^2 \geq (\text{Radius}_i + \text{Radius}_j)^2, \quad \forall i, j \in C, i < j \)

# Type
# The problem has continuous variables. The non-overlapping constraint is a non-convex
# quadratic constraint, making this a Non-Convex Quadratically Constrained Program (QCQP),
# a specific type of Nonlinear Program (NLP).
Type:
Continuous, Quadratic, Non-linear
NLP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_nonlinear_container_packing(
    container_ids=list(range(6)),
    diameters={0: 1.2, 1: 1.1, 2: 1.0, 3: 0.9, 4: 0.8, 5: 0.7},
    truck_width=2.5,
    time_limit=60
):
    """
    Solves a non-linear container packing problem for a reduced number of containers.
    This model is a Non-Convex Quadratically Constrained Program (QCQP).
    """
    radii = {c: d / 2 for c, d in diameters.items()}

    model = gp.Model("NonlinearContainerPacking")

    x = model.addVars(container_ids, name="x_coord", lb=0.0)
    y = model.addVars(container_ids, name="y_coord", lb=0.0)
    L = model.addVar(name="TruckLength", lb=0.0)

    model.setObjective(L, GRB.MINIMIZE)

    for c in container_ids:
        model.addConstr(y[c] - radii[c] >= 0, name=f"width_lower_{c}")
        model.addConstr(y[c] + radii[c] <= truck_width, name=f"width_upper_{c}")
        model.addConstr(x[c] - radii[c] >= 0, name=f"length_lower_{c}")
        model.addConstr(x[c] + radii[c] <= L, name=f"length_upper_{c}")

    for i in container_ids:
        for j in container_ids:
            if i < j:
                model.addQConstr(
                    (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) >= (radii[i] + radii[j])**2,
                    name=f"no_overlap_{i}_{j}"
                )

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

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


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


Solution status: OPTIMAL
Optimal objective value: 2.373934757