A shared mobility company plans to deploy a fleet of 1,000 electric scooters across five key hotspot zones in a city. To ensure service stability and a positive user experience, the company aims to achieve a balanced deployment that aligns with the demand profile of each zone.

The company's goal is to minimize the network's imbalance. This imbalance is defined as the sum of the squared differences between the number of scooters deployed in each zone and an ideal, attractiveness-weighted deployment. The ideal deployment for a zone is calculated by distributing the total fleet proportionally to that zone's share of the total attractiveness.

When planning the deployment, the following conditions must be met:
1. The total number of scooters deployed across all zones must equal 1,000.
2. To align operational capacity with potential demand, the number of scooters in each zone must not be less than a minimum quota. This quota is set at 10% of the total fleet size, weighted by the proportion of that zone's attractiveness relative to the total attractiveness of all zones.
3. The 'Commercial Center' and 'Transit Hub' zones are geographically adjacent and frequently interact with each other. To ensure service continuity, the absolute difference in the number of scooters deployed in these two zones must not exceed 50.
4. The company has divided the zones into two operational clusters: Cluster 1 (Commercial Center, Tourist Area) and Cluster 2 (University City, Tech Park, Transit Hub). Each cluster can deploy a maximum of 600 scooters to match the management capacity of local operational teams.

The attractiveness data for each zone is provided in the table below. Please devise a deployment plan that minimizes the network imbalance while satisfying all constraints.

Table: Zonal Attractiveness Data

| Hotspot Zone | Attractiveness (Predicted Daily Trips) |
|:---|:---|
| Commercial Center | 500 |
| University City | 800 |
| Tech Park | 700 |
| Tourist Area | 600 |
| Transit Hub | 900 |

Problem type: NLP (Toy: 5 variables, 10 constraints, 19 nonzeros)

Mathematical model:
Set:
1. Zones
The set of hotspot zones, \( I = \{\text{Commercial Center, University City, Tech Park, Tourist Area, Transit Hub}\} \).

Parameters:
1.  TotalScooters
    The total number of scooters to be deployed.
    1000
2.  Attractiveness
    A dictionary mapping each zone \(i \in I\) to its attractiveness value.
     \(\{\text{Commercial Center: 500, University City: 800, Tech Park: 700, Tourist Area: 600, Transit Hub: 900}\}\)
3.  TotalAttractiveness
    The sum of all attractiveness values.
    \(500 + 800 + 700 + 600 + 900 = 3500\)
4.  MinQuotaFactor
    The minimum allocation factor based on total fleet size.
    0.1
5.  TargetDeployment
    The ideal, attractiveness-weighted deployment for each zone \(i \in I\).
    \(\text{TargetDeployment}_i = \text{TotalScooters} \times \frac{\text{Attractiveness}_i}{\text{TotalAttractiveness}}\)

Decision Variable:
1.  Deployment
    Integer variable, \(n_i \ge 0\) for each zone \(i \in I\), representing the number of scooters to be deployed in that zone.

Objective:
1.  Minimize the network imbalance, defined as the sum of squared deviations from the target deployment.
    min: \(\sum_{i \in I} (n_i - \text{TargetDeployment}_i)^2\)

Constraints:
1.  Total Deployment Constraint
    The sum of scooters deployed in all zones must equal the total number of available scooters.
    \(\sum_{i \in I} n_i = \text{TotalScooters}\)
2.  Minimum Quota Constraint
    The number of scooters in each zone must be greater than or equal to a minimum quota, which is weighted by the zone's relative attractiveness.
    \(n_i \ge \text{TotalScooters} \times \text{MinQuotaFactor} \times \frac{\text{Attractiveness}_i}{\text{TotalAttractiveness}} \quad \forall i \in I\)
3.  Adjacency Constraint (New)
    The absolute difference in deployment between 'Commercial Center' and 'Transit Hub' must not exceed 50.
    \(|n_{\text{Commercial Center}} - n_{\text{Transit Hub}}| \le 50\)
4.  Operational Cluster Capacity Constraint (New)
    The total number of scooters in each operational cluster cannot exceed its capacity of 600.
    \(n_{\text{Commercial Center}} + n_{\text{Tourist Area}} \le 600\)
    \(n_{\text{University City}} + n_{\text{Tech Park}} + n_{\text{Transit Hub}} \le 600\)

Type:
Integer, Non-linear, Linear
NLP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB
from typing import List, Dict

def solve_scooter_deployment(
    zones: List[str] = ['Commercial Center', 'University City', 'Tech Park', 'Tourist Area', 'Transit Hub'],
    attractiveness: Dict[str, int] = {
        'Commercial Center': 500,
        'University City': 800,
        'Tech Park': 700,
        'Tourist Area': 600,
        'Transit Hub': 900
    },
    total_scooters: int = 1000,
    min_quota_factor: float = 0.1,
    adjacency_limit: int = 50,
    cluster_capacity: int = 600
):
    """
    Solves the shared e-scooter network balancing problem using Gurobi.

    Args:
        zones (List[str]): A list of hotspot zone names.
        attractiveness (Dict[str, int]): A dictionary mapping each zone to its attractiveness value.
        total_scooters (int): The total number of scooters to be deployed.
        min_quota_factor (float): The factor of the total fleet size used for calculating the minimum deployment quota.
        adjacency_limit (int): The maximum allowed difference in deployment for adjacent key zones.
        cluster_capacity (int): The maximum number of scooters an operational cluster can manage.
    """
    # --- 1. Setup and Pre-calculation ---
    num_zones = len(zones)
    if num_zones == 0:
        return {"status": "error", "message": "The list of zones cannot be empty."}

    total_attractiveness = sum(attractiveness.values())

    # --- 2. Gurobi Model Initialization ---
    model = gp.Model("ScooterDeployment_Complex")

    # --- 3. Define Decision Variables ---
    # n[i]: number of scooters deployed in zone i
    n = model.addVars(zones, name="deployment", vtype=GRB.INTEGER, lb=0)

    # --- 4. Set Objective Function ---
    # Objective: Minimize the deviation from an ideal, attractiveness-weighted deployment.
    target_deployments = {}
    if total_attractiveness > 0:
        for i in zones:
            target_deployments[i] = total_scooters * (attractiveness[i] / total_attractiveness)
    else: # Handle case of zero total attractiveness
        avg_deployment = total_scooters / num_zones
        for i in zones:
            target_deployments[i] = avg_deployment

    # The objective is the sum of squared differences from this new weighted target.
    imbalance = gp.quicksum((n[i] - target_deployments[i]) * (n[i] - target_deployments[i]) for i in zones)
    model.setObjective(imbalance, GRB.MINIMIZE)

    # --- 5. Add Constraints ---
    # Constraint 1: Total Deployment Constraint
    model.addConstr(gp.quicksum(n[i] for i in zones) == total_scooters, "TotalDeployment")

    # Constraint 2: Minimum Quota Constraint
    if total_attractiveness > 0:
        for i in zones:
            min_quota = total_scooters * min_quota_factor * (attractiveness[i] / total_attractiveness)
            model.addConstr(n[i] >= min_quota, f"MinQuota_{i}")

    # Constraint 3: Adjacency Constraint
    # The absolute difference between 'Commercial Center' and 'Transit Hub' must not exceed the limit.
    model.addConstr(n['Commercial Center'] - n['Transit Hub'] <= adjacency_limit, "Adjacency_Upper")
    model.addConstr(n['Commercial Center'] - n['Transit Hub'] >= -adjacency_limit, "Adjacency_Lower")

    # Constraint 4: Operational Cluster Capacity Constraints
    # Cluster 1: Commercial Center, Tourist Area
    model.addConstr(n['Commercial Center'] + n['Tourist Area'] <= cluster_capacity, "Cluster1_Capacity")
    # Cluster 2: University City, Tech Park, Transit Hub
    model.addConstr(n['University City'] + n['Tech Park'] + n['Transit Hub'] <= cluster_capacity, "Cluster2_Capacity")
    
    # --- 6. Solve the Model ---
    model.optimize()

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


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


Solution status: OPTIMAL
Optimal objective value: 6122.979591836687