A delivery company is using a drone to make a series of urgent deliveries to 6 specific customer locations (C1 to C6) within a dense urban area. The drone must start from its depot, visit each of the 6 customer locations exactly once to drop off a package, and then return to the depot. The company has calculated the flight time (in minutes) between all pairs of locations (depot and customer sites), considering air-traffic restrictions and optimal flight paths.

Flight Time Matrix (Minutes - Symmetric):

|        | Depot | C1    | C2    | C3    | C4    | C5    | C6    |
| :----- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
| Depot  | -     | 8     | 12    | 15    | 7     | 10    | 14    |
|   C1   | 8     | -     | 5     | 11    | 6     | 9     | 13    |
|   C2   | 12    | 5     | -     | 7     | 10    | 4     | 8     |
|   C3   | 15    | 11    | 7     | -     | 9     | 6     | 5     |
|   C4   | 7     | 6     | 10    | 9     | -     | 3     | 11    |
|   C5   | 10    | 9     | 4     | 6     | 3     | -     | 7     |
|   C6   | 14    | 13    | 8     | 5     | 11    | 7     | -     |

Problem type: ILP (Medium: 48 variables, 44 constraints, 174 nonzeros)

Mathematical model:
Set:
1. CustomerLocation 
The set of customer locations, \( C = \{C1, C2, C3, C4, C5, C6\} \) 
2. Location
The set of locations, \( L = \{Depot\} \cup C \)

Parameter:
1. FlightTime
[[0, 8, 12, 15, 7, 10, 14], 
 [8, 0, 5, 11, 6, 9, 13], 
 [12, 5, 0, 7, 10, 4, 8], 
 [15, 11, 7, 0, 9, 6, 5], 
 [7, 6, 10, 9, 0, 3, 11], 
 [10, 9, 4, 6, 3, 0, 7], 
 [14, 13, 8, 5, 11, 7, 0]] # flight time in minutes between locations

Decision variable:
1. Route
Binary variable, \( Route[i][j] \) for \( i, j \in L \), where \( Route[i][j] = 1 \) if the drone travels directly from location \( i \) to location \( j \), and 0 otherwise.

Objective:
1. Minimize the total flight time.
min: \sum_{i \in L, j \in L} (FlightTime[i][j] * Route[i][j])

Constraint:
1. Each customer location must be visited exactly once. 
\sum_{i \in L} Route[i][j] = 1 \forall j \in C 
2. The drone must start from and return to the depot after visiting all customer locations.
\sum_{j \in C} Route[Depot][j] = 1
\sum_{i \in C} Route[i][Depot] = 1
3. Subtour elimination constraints (to prevent subtours).
\sum_{i \in C} Route[i][j] - \sum_{k \in C} Route[j][k] = 0 \forall j \in C, j \neq Depot

Type:
Binary, Linear, Linear
ILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_drone_tsp(
    flight_times_input=[
        [0, 8, 12, 15, 7, 10, 14],
        [8, 0, 5, 11, 6, 9, 13],
        [12, 5, 0, 7, 10, 4, 8],
        [15, 11, 7, 0, 9, 6, 5],
        [7, 6, 10, 9, 0, 3, 11],
        [10, 9, 4, 6, 3, 0, 7],
        [14, 13, 8, 5, 11, 7, 0]
    ]
):
    """
    Solves the Drone Traveling Salesperson Problem (TSP).
    """
    try:
        # --- Parameters ---
        customer_locations_set = {"C1", "C2", "C3", "C4", "C5", "C6"}
        depot_name = "Depot"
        location_names = [depot_name] + sorted(list(customer_locations_set))
        num_total_locations = len(location_names)
        depot_index = 0
        customer_indices = list(range(1, num_total_locations))

        flight_time_matrix = {
            (i, j): flight_times_input[i][j]
            for i in range(num_total_locations)
            for j in range(num_total_locations)
        }

        # --- Create a new model ---
        model = gp.Model("DroneTSP")

        # --- Decision Variables ---
        arcs = [(i, j) for i in range(num_total_locations) for j in range(num_total_locations) if i != j]
        x = model.addVars(arcs, vtype=GRB.BINARY, name="Route")

        # --- Objective Function ---
        model.setObjective(gp.quicksum(flight_time_matrix[i, j] * x[i, j] for i, j in arcs), GRB.MINIMIZE)

        # --- Constraints ---
        model.addConstr(gp.quicksum(x[depot_index, j] for j in customer_indices) == 1, "LeaveDepot")
        model.addConstr(gp.quicksum(x[i, depot_index] for i in customer_indices) == 1, "ReturnToDepot")

        for j in customer_indices:
            model.addConstr(gp.quicksum(x[i, j] for i in range(num_total_locations) if i != j) == 1, f"EnterCustomer_{j}")

        for i in customer_indices:
            model.addConstr(gp.quicksum(x[i, j] for j in range(num_total_locations) if i != j) == 1, f"LeaveCustomer_{i}")

        num_customers = len(customer_indices)
        u = model.addVars(customer_indices, lb=1.0, ub=float(num_customers), vtype=GRB.CONTINUOUS, name="u")

        for i in customer_indices:
            for j in customer_indices:
                if i != j:
                    model.addConstr(u[i] - u[j] + num_customers * x[i, j] <= num_customers - 1, f"MTZ_{i}_{j}")

        # --- Optimize model ---
        model.optimize()

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

    except gp.GurobiError as e:
        return {"status": f"Gurobi error: {e}"}
    except Exception as e:
        return {"status": f"An unexpected error occurred: {e}"}


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


Solution status: OPTIMAL
Optimal objective value: 42