A container terminal needs to allocate berths (which are arranged in a straight line with a total length of 900) for 9 vessels (A, B, C, D, E, F, G, H, I) arriving within 4 days. The lengths of the vessels are 287, 196, 261, 288, 208, 238, 190, 217 and 254 respectively. The arrival times of the vessels are 9, 27, 34, 40, 1, 31, 28, 20, and 22 hours respectively, while the required working times for loading and unloading after docking are 7, 31, 31, 25, 29, 11, 28, 24, and 20 hours respectively. The optimal berthing positions for the vessels are 375, 419, 591, 510, 567, 64, 507, 377, and 93 respectively. A berthing plan now needs to be devised, where the additional operational costs caused by deviations from the original optimal berthing positions are 8, 4, 5, 5, 2, 6, 8, 6, and 8 per unit of distance, and the delay costs are 7, 7, 3, 4, 4, 7, 2, 4, and 7 per hour of delay. What is the minimum total additional cost resulting from deviations from the optimal berthing positions and delays? (Assume vessels can only dock at integer positions on the hour.)

Problem type: ILP (Medium: 171 variables, 225 constraints, 639 nonzeros)

Mathematical model:
Set:
1. Vessels
The set of vessels, \( V = \{A, B, C, D, E, F, G, H, I\} \)

Parameter:
1. BerthLength
900
2. VesselNum
9
3. BestBerthLocation
[375, 419, 591, 510, 567, 64, 507, 377, 93]
4. ExpectedArrivalTime
[9, 27, 34, 40, 1, 31, 28, 20, 22]
5. OperationTime
[7, 31, 31, 25, 29, 11, 28, 24, 20]
6. VesselLength
[287, 196, 261, 288, 208, 238, 190, 217, 254]
7. AdditionalTravelCost
[8, 4, 5, 5, 2, 6, 8, 6, 8]
8. PenaltyCost
[7, 7, 3, 4, 4, 7, 2, 4, 7]
9. Period
96
10. M (a large number)
2000

Decision variable:
1. BerthLocation
Integer variable, \( BerthLocation_{i} \) for all \( i \in V \), representing the berth location of vessel \( i \)
2. BerthTime
Integer variable, \( BerthTime_{i} \) for all \( i \in V \), representing the berthing time of vessel \( i \)
3. LocationRelation
Binary variable, \( LocationRelation_{ij} \) for all \( i, j \in V \), representing the vessel \( i \) located to the left-hand side of vessel \( j \) (1 if true, 0 otherwise)
4. TimeRelation
Binary variable, \( TimeRelation_{ij} \) for all \( i, j \in V \), representing the vessel \( i \) berthing before vessel \( j \) (1 if true, 0 otherwise)

Objective:
The total distance between vessels to the yard and the total penalty cost incurred due to delays
min: \sum_{i\in V}[AdditionalTravelCost_{j}\cdot\left|BerthLocation_{i} - BestBerthLocation_{j}\right| + PenaltyCost_{i} \cdot (BerthTime_{i} - ExpectedArrivalTime_{i}, 0)]

Constraint:
1. MaxBerthLocation
x_{i} + VesselLength_{i} \leq BerthLength, \forall i \in V
2. BerthLocationRelation
x_{i} + VesselLength_{i} \leq x_{j} + M \cdot (1 - LocationRelation_{ij}), \forall i, j \in V, i \neq j
3. BerthTimeRelation
BerthTime_{i} + OperationTime_{i} \leq BerthTime_{j} + M \cdot (1 - TimeRelation_{ij}), \forall i, j \in V, i \neq j
4. Relation
LocationRelation_{ij} + LocationRelation_{ji} + TimeRelation_{ij} + TimeRelation_{ji} \geq 1, \forall i, j \in V, i < j
5. BerthTime
BerthTime_{i} \geq ExpectedArrivalTime_{i}, \forall i \in V
6. BerthLocation
BerthLocation_{i} \geq 0, \forall i \in V


Type:
Integer, Linear, Linear
ILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_vessel_berth_allocation(
    berth_length=900,
    best_berth_location=[375, 419, 591, 510, 567, 64, 507, 377, 93],
    expected_arrival_time=[9, 27, 34, 40, 1, 31, 28, 20, 22],
    operation_time=[7, 31, 31, 25, 29, 11, 28, 24, 20],
    vessel_length=[287, 196, 261, 288, 208, 238, 190, 217, 254],
    additional_travel_cost=[8, 4, 5, 5, 2, 6, 8, 6, 8],
    penalty_cost=[7, 7, 3, 4, 4, 7, 2, 4, 7],
    period=96,  # Note: This parameter is not used in the current model logic
    big_m=2000
):
    """
    Models and solves the Vessel Berth Allocation Problem (VBAP).
    """
    # --- 1. Model Creation ---
    model = gp.Model("VesselBerthAllocation")

    # --- 2. Sets ---
    # Derive the set of vessels from the length of the input data lists
    vessels = range(len(vessel_length))
    vessel_pairs = [(i, j) for i in vessels for j in vessels if i != j]

    # --- 3. Decision Variables ---
    # berth_location: position of each vessel along the quay
    berth_location = model.addVars(vessels, vtype=GRB.INTEGER, lb=0, name="berth_location")

    # berth_time: start time of berthing for each vessel
    berth_time = model.addVars(vessels, vtype=GRB.INTEGER, lb=0, name="berth_time")

    # location_relation[i,j]: 1 if vessel i is to the left of vessel j, 0 otherwise
    location_relation = model.addVars(vessel_pairs, vtype=GRB.BINARY, name="location_relation")

    # time_relation[i,j]: 1 if vessel i is berthed before vessel j, 0 otherwise
    time_relation = model.addVars(vessel_pairs, vtype=GRB.BINARY, name="time_relation")

    # position_deviation: auxiliary variable for absolute value of location deviation
    position_deviation = model.addVars(vessels, vtype=GRB.INTEGER, lb=0, name="position_deviation")

    # --- 4. Objective Function ---
    # Minimize total cost (travel deviation cost + delay penalty cost)
    objective = gp.quicksum(
        additional_travel_cost[i] * position_deviation[i] +
        penalty_cost[i] * (berth_time[i] - expected_arrival_time[i])
        for i in vessels
    )
    model.setObjective(objective, GRB.MINIMIZE)

    # --- 5. Constraints ---
    # Constraint 1: Linearize the absolute value for position deviation
    for i in vessels:
        model.addConstr(position_deviation[i] >= berth_location[i] - best_berth_location[i], name=f"abs_pos_dev_{i}")
        model.addConstr(position_deviation[i] >= best_berth_location[i] - berth_location[i], name=f"abs_neg_dev_{i}")

    # Constraint 2: Vessels must be fully within the berth's length
    for i in vessels:
        model.addConstr(berth_location[i] + vessel_length[i] <= berth_length, name=f"max_berth_loc_{i}")

    # Constraint 3: No spatial overlap between vessels (Big-M)
    for i, j in vessel_pairs:
        model.addConstr(
            berth_location[i] + vessel_length[i] <= berth_location[j] + big_m * (1 - location_relation[i, j]),
            name=f"loc_rel_{i}_{j}"
        )

    # Constraint 4: No temporal overlap between vessels (Big-M)
    for i, j in vessel_pairs:
        model.addConstr(
            berth_time[i] + operation_time[i] <= berth_time[j] + big_m * (1 - time_relation[i, j]),
            name=f"time_rel_{i}_{j}"
        )

    # Constraint 5: Vessels must not overlap in both space and time simultaneously.
    # For any pair of vessels (i, j), they must be separated either in space or in time.
    for i in vessels:
        for j in vessels:
            if i < j:
                model.addConstr(
                    location_relation[i, j] + location_relation[j, i] +
                    time_relation[i, j] + time_relation[j, i] >= 1,
                    name=f"no_overlap_{i}_{j}"
                )

    # Constraint 6: Vessels cannot be berthed before their expected arrival time
    for i in vessels:
        model.addConstr(berth_time[i] >= expected_arrival_time[i], name=f"arrival_time_{i}")

    # --- 6. Solve the Model ---
    model.setParam("OutputFlag", 0)  # Suppress Gurobi output
    model.optimize()

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


# Run the solver function
if __name__ == "__main__":
    result = solve_vessel_berth_allocation()
    print(result)


Solution status: OPTIMAL
Optimal objective value: 729