Problem Description:
A city needs to place emergency response facilities to ensure that every residential area can be reached within 10 minutes. There are 5 potential facility locations (F1-F5) and 8 residential areas (R1-R8). The travel time (in minutes) between each facility location and residential area is given in the following table:

| Facility\Residential | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
|----------------------|----|----|----|----|----|----|----|----|
| F1                   | 8  | 12 | 9  | 15 | 7  | 11 | 14 | 6  |
| F2                   | 10 | 8  | 13 | 7  | 9  | 12 | 5  | 11 |
| F3                   | 14 | 6  | 10 | 8  | 12 | 7  | 9  | 13 |
| F4                   | 7  | 11 | 14 | 6  | 8  | 10 | 12 | 9  |
| F5                   | 9  | 13 | 7  | 11 | 6  | 14 | 8  | 10 |

The goal is to select the minimum number of facilities such that every residential area is covered (reachable within 10 minutes) by at least one selected facility.

Problem type: ILP (Small: 5 variables, 8 constraints, 24 nonzeros)

Mathematical model:
Set:
1. Facilities
The set of potential facility locations, \( F = \{F1, F2, F3, F4, F5\} \)
2. Residentials
The set of residential areas, \( R = \{R1, R2, R3, R4, R5, R6, R7, R8\} \)

Parameter:
1. Coverage
Binary parameter \( a_{fr} \in \{0, 1\} \) for all \( f \in F, r \in R \), where \( a_{fr} = 1 \) if facility \( f \) can cover residential \( r \) within 10 minutes, and \( a_{fr} = 0 \) otherwise. This is derived from the travel time table (e.g., \( a_{F1,R1} = 1 \) since 8 ≤ 10, but \( a_{F1,R2} = 0 \) since 12 > 10).

Decision variable:
1. FacilitySelected
Binary variable \( x_f \in \{0, 1\} \) for all \( f \in F \), where \( x_f = 1 \) if facility \( f \) is selected, and \( x_f = 0 \) otherwise

Objective:
The minimum number of facilities to select
min: \sum_{f\in F}x_f

Constraint:
1. Each residential area must be covered by at least one selected facility
\sum_{f\in F}a_{fr} \cdot x_f \geq 1, \forall r \in R

Type:
Binary, Linear, Linear
ILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_facility_location(
    facilities=['F1', 'F2', 'F3', 'F4', 'F5'],
    residentials=['R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8'],
    travel_time={
        'F1': [8, 12, 9, 15, 7, 11, 14, 6],
        'F2': [10, 8, 13, 7, 9, 12, 5, 11],
        'F3': [14, 6, 10, 8, 12, 7, 9, 13],
        'F4': [7, 11, 14, 6, 8, 10, 12, 9],
        'F5': [9, 13, 7, 11, 6, 14, 8, 10]
    }
):
    """
    Solves the facility location problem.
    """
    # Create coverage parameter a_fr (1 if travel time <=10, 0 otherwise)
    coverage = {}
    for f in facilities:
        for i, r in enumerate(residentials):
            coverage[(f, r)] = 1 if travel_time[f][i] <= 10 else 0

    # Create model
    model = gp.Model("Facility_Location")

    # Decision variables: x_f (whether facility f is selected)
    x = model.addVars(facilities, vtype=GRB.BINARY, name="Select")

    # Objective: minimize number of selected facilities
    model.setObjective(gp.quicksum(x[f] for f in facilities), GRB.MINIMIZE)

    # Constraints: each residential area must be covered by at least one selected facility
    for r in residentials:
        model.addConstr(gp.quicksum(coverage[(f, r)] * x[f] for f in facilities) >= 1,
                        f"Cover_{r}")

    # 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_facility_location()
    print(result)

Solution status: OPTIMAL
Optimal objective value: 2