# Complete Optimization Problem and Solution: train_station

## 1. Problem Context and Goals

### Context  
The primary objective is to optimize the allocation of trains to various stations to maximize the number of passengers served. This involves making decisions about which trains should be assigned to which stations. Each decision is represented by a binary choice, indicating whether a specific train is assigned to a particular station. The operational goal is to maximize the total number of passengers served, which is calculated by summing the passenger demand for each train-station assignment. The business configuration includes a threshold for platform utilization, which determines when additional resources are needed. This threshold ensures that existing platforms are utilized efficiently before expanding capacity. The calculation of total passengers served is based on the demand for each train at each station and the assignments made, aiming to maximize throughput. The data reflects current operational conditions, focusing on linear decision-making processes. Resource limitations, such as the number of platforms available at each station, are considered to ensure that the optimization remains within feasible bounds. The business configuration parameters, such as platform utilization thresholds, are integral to the decision-making process.

### Goals  
The optimization goal is to maximize the total number of passengers served by strategically assigning trains to stations. The metric for optimization is the total passenger count, which is determined by the demand for each train at each station and the corresponding assignments. Success is measured by the ability to maximize this passenger count while adhering to resource constraints and compatibility requirements. The goal is articulated in straightforward business terms, focusing on maximizing passenger throughput without delving into complex mathematical expressions.

## 2. Constraints    

The constraints are designed to ensure that the optimization problem adheres to practical and operational limits:

- Each station has a limited number of platforms, and the total number of trains assigned to a station cannot exceed this number. This ensures that the station's capacity is not exceeded.
- A train can only be assigned to a station if its service is compatible with that station. This constraint ensures that operational compatibility is maintained.
- Each train can be assigned to at most one station, ensuring that no train is over-allocated and that assignments are exclusive.

These constraints are expressed in business terms that naturally align with linear mathematical formulations, avoiding any complex relationships such as variable products or divisions.

## 3. Available Data  

### Database Schema  
```sql
-- Iteration 1 Database Schema
-- Objective: Schema changes include adding tables for service compatibility and passenger demand, modifying existing tables for better mapping, and updating configuration logic for scalar parameters and formulas.

CREATE TABLE station (
  Station_ID INTEGER,
  Total_Passengers INTEGER,
  Number_of_Platforms INTEGER
);

CREATE TABLE train_station (
  Train_ID INTEGER,
  Station_ID INTEGER,
  Service_Compatible BOOLEAN
);

CREATE TABLE service_compatibility (
  Train_ID INTEGER,
  Station_ID INTEGER,
  Compatible BOOLEAN
);

CREATE TABLE passenger_demand (
  Train_ID INTEGER,
  Station_ID INTEGER,
  Demand INTEGER
);
```

### Data Dictionary  
The data dictionary provides a business-oriented view of the tables and columns, linking them to their roles in the optimization process:

- **Station Table**: Contains information about each station, including the total number of passengers it can serve and the number of platforms available. This data is crucial for setting constraints on platform availability and for calculating the objective of maximizing passenger throughput.
  
- **Train_Station Table**: Records the assignments of trains to stations, indicating whether a train's service is compatible with a station. This table is central to defining decision variables in the optimization problem.
  
- **Service_Compatibility Table**: Details the compatibility of train services with stations, ensuring that only compatible assignments are made. This data enforces service compatibility constraints.
  
- **Passenger_Demand Table**: Captures the demand for each train at each station, which is used to calculate the potential passenger throughput for different assignments. This data is integral to the objective function.

### Current Stored Values  
```sql
-- Iteration 1 Realistic Data
-- Generated by triple expert (business + data + optimization)
-- Values were determined based on typical train station operations, ensuring a balance between demand, platform availability, and service compatibility.

-- Realistic data for station
INSERT INTO station (Station_ID, Total_Passengers, Number_of_Platforms) VALUES (1, 1200, 3);
INSERT INTO station (Station_ID, Total_Passengers, Number_of_Platforms) VALUES (2, 1800, 4);
INSERT INTO station (Station_ID, Total_Passengers, Number_of_Platforms) VALUES (3, 2200, 5);

-- Realistic data for train_station
INSERT INTO train_station (Train_ID, Station_ID, Service_Compatible) VALUES (101, 1, True);
INSERT INTO train_station (Train_ID, Station_ID, Service_Compatible) VALUES (102, 2, True);
INSERT INTO train_station (Train_ID, Station_ID, Service_Compatible) VALUES (103, 3, False);
INSERT INTO train_station (Train_ID, Station_ID, Service_Compatible) VALUES (101, 2, True);
INSERT INTO train_station (Train_ID, Station_ID, Service_Compatible) VALUES (102, 3, True);

-- Realistic data for service_compatibility
INSERT INTO service_compatibility (Train_ID, Station_ID, Compatible) VALUES (101, 1, True);
INSERT INTO service_compatibility (Train_ID, Station_ID, Compatible) VALUES (102, 2, True);
INSERT INTO service_compatibility (Train_ID, Station_ID, Compatible) VALUES (103, 3, False);
INSERT INTO service_compatibility (Train_ID, Station_ID, Compatible) VALUES (101, 2, True);
INSERT INTO service_compatibility (Train_ID, Station_ID, Compatible) VALUES (102, 3, True);

-- Realistic data for passenger_demand
INSERT INTO passenger_demand (Train_ID, Station_ID, Demand) VALUES (101, 1, 250);
INSERT INTO passenger_demand (Train_ID, Station_ID, Demand) VALUES (102, 2, 350);
INSERT INTO passenger_demand (Train_ID, Station_ID, Demand) VALUES (103, 3, 150);
INSERT INTO passenger_demand (Train_ID, Station_ID, Demand) VALUES (101, 2, 300);
INSERT INTO passenger_demand (Train_ID, Station_ID, Demand) VALUES (102, 3, 400);
```

## 4. Mathematical Optimization Formulation

#### Decision Variables
- Let \( x_{ij} \) be a binary decision variable where \( x_{ij} = 1 \) if train \( i \) is assigned to station \( j \), and \( x_{ij} = 0 \) otherwise.

#### Objective Function
Maximize the total number of passengers served:
\[
\text{Maximize } \sum_{i,j} \text{Demand}_{ij} \times x_{ij}
\]
where \(\text{Demand}_{ij}\) is the passenger demand for train \( i \) at station \( j \).

#### Constraints
1. **Platform Capacity Constraint**: The number of trains assigned to each station cannot exceed the number of platforms available at that station.
   \[
   \sum_{i} x_{ij} \leq \text{Number\_of\_Platforms}_j \quad \forall j
   \]

2. **Service Compatibility Constraint**: A train can only be assigned to a station if it is compatible with that station.
   \[
   x_{ij} \leq \text{Compatible}_{ij} \quad \forall i, j
   \]

3. **Exclusive Assignment Constraint**: Each train can be assigned to at most one station.
   \[
   \sum_{j} x_{ij} \leq 1 \quad \forall i
   \]

Data Source Verification:
- \(\text{Demand}_{ij}\) comes from `passenger_demand.Demand`.
- \(\text{Number\_of\_Platforms}_j\) comes from `station.Number_of_Platforms`.
- \(\text{Compatible}_{ij}\) comes from `service_compatibility.Compatible`.

This formulation provides a complete and immediately solvable linear mathematical model using the provided data, ensuring that all constraints and the objective function are linear and adhere to the problem's requirements.

## 5. Gurobipy Implementation

```python
# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_train_station_allocation():
    """Optimize train allocation to stations to maximize passengers served."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("train_station")
    
    # Data from the problem context
    stations = [1, 2, 3]
    trains = [101, 102, 103]
    
    # Passenger demand for each train at each station
    demand = {
        (101, 1): 250, (101, 2): 300, (101, 3): 0,
        (102, 1): 0, (102, 2): 350, (102, 3): 400,
        (103, 1): 0, (103, 2): 0, (103, 3): 150
    }
    
    # Number of platforms available at each station
    platforms = {1: 3, 2: 4, 3: 5}
    
    # Compatibility of train services with stations
    compatibility = {
        (101, 1): True, (101, 2): True, (101, 3): False,
        (102, 1): False, (102, 2): True, (102, 3): True,
        (103, 1): False, (103, 2): False, (103, 3): False
    }
    
    # Validate data lengths
    assert len(demand) == len(trains) * len(stations), "Demand data length mismatch"
    assert len(platforms) == len(stations), "Platform data length mismatch"
    assert len(compatibility) == len(trains) * len(stations), "Compatibility data length mismatch"
    
    # 2. VARIABLES
    # Decision variables: x[i, j] = 1 if train i is assigned to station j
    x = model.addVars(trains, stations, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total number of passengers served
    model.setObjective(gp.quicksum(demand[i, j] * x[i, j] for i in trains for j in stations), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Platform Capacity Constraint
    model.addConstrs((gp.quicksum(x[i, j] for i in trains) <= platforms[j] for j in stations), name="platform_capacity")
    
    # Service Compatibility Constraint
    model.addConstrs((x[i, j] <= compatibility[i, j] for i in trains for j in stations), name="service_compatibility")
    
    # Exclusive Assignment Constraint
    model.addConstrs((gp.quicksum(x[i, j] for j in stations) <= 1 for i in trains), name="exclusive_assignment")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in trains:
            for j in stations:
                if x[i, j].x > 1e-6:
                    print(f"Train {i} assigned to Station {j} with demand {demand[i, j]}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_train_station_allocation()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 700.0
**Execution Time**: 0.61 seconds
**Reliability**: high
**Analysis**: Gurobipy successfully found an optimal solution with a high objective value in a short execution time, indicating efficient performance and reliability.

## 6. DOCplex Implementation

```python
# Complete DOCPLEX implementation

from docplex.mp.model import Model

def train_station_optimization():
    """Optimize train assignments to stations to maximize passengers served."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="train_station")
    
    # Data from the database
    stations = [
        {"Station_ID": 1, "Total_Passengers": 1200, "Number_of_Platforms": 3},
        {"Station_ID": 2, "Total_Passengers": 1800, "Number_of_Platforms": 4},
        {"Station_ID": 3, "Total_Passengers": 2200, "Number_of_Platforms": 5}
    ]
    
    train_station_data = [
        {"Train_ID": 101, "Station_ID": 1, "Service_Compatible": True},
        {"Train_ID": 102, "Station_ID": 2, "Service_Compatible": True},
        {"Train_ID": 103, "Station_ID": 3, "Service_Compatible": False},
        {"Train_ID": 101, "Station_ID": 2, "Service_Compatible": True},
        {"Train_ID": 102, "Station_ID": 3, "Service_Compatible": True}
    ]
    
    service_compatibility = [
        {"Train_ID": 101, "Station_ID": 1, "Compatible": True},
        {"Train_ID": 102, "Station_ID": 2, "Compatible": True},
        {"Train_ID": 103, "Station_ID": 3, "Compatible": False},
        {"Train_ID": 101, "Station_ID": 2, "Compatible": True},
        {"Train_ID": 102, "Station_ID": 3, "Compatible": True}
    ]
    
    passenger_demand = [
        {"Train_ID": 101, "Station_ID": 1, "Demand": 250},
        {"Train_ID": 102, "Station_ID": 2, "Demand": 350},
        {"Train_ID": 103, "Station_ID": 3, "Demand": 150},
        {"Train_ID": 101, "Station_ID": 2, "Demand": 300},
        {"Train_ID": 102, "Station_ID": 3, "Demand": 400}
    ]
    
    # Extract unique train and station IDs
    train_ids = set(d["Train_ID"] for d in passenger_demand)
    station_ids = set(d["Station_ID"] for d in stations)
    
    # 2. VARIABLES
    x = {(i, j): mdl.binary_var(name=f"x_{i}_{j}") for i in train_ids for j in station_ids}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(d["Demand"] * x[d["Train_ID"], d["Station_ID"]] for d in passenger_demand)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Platform Capacity Constraint
    for station in stations:
        station_id = station["Station_ID"]
        mdl.add_constraint(
            mdl.sum(x[i, station_id] for i in train_ids) <= station["Number_of_Platforms"],
            ctname=f"platform_capacity_{station_id}"
        )
    
    # Service Compatibility Constraint
    for sc in service_compatibility:
        if not sc["Compatible"]:
            mdl.add_constraint(
                x[sc["Train_ID"], sc["Station_ID"]] == 0,
                ctname=f"service_compatibility_{sc['Train_ID']}_{sc['Station_ID']}"
            )
    
    # Exclusive Assignment Constraint
    for train_id in train_ids:
        mdl.add_constraint(
            mdl.sum(x[train_id, j] for j in station_ids) <= 1,
            ctname=f"exclusive_assignment_{train_id}"
        )
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in train_ids:
            for j in station_ids:
                value = solution.get_value(x[i, j])
                if value > 1e-6:
                    print(f"x[{i},{j}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
train_station_optimization()
```

### Execution Results
**Status**: OPTIMAL
**Optimal Value**: 700.0
**Execution Time**: 3.43 seconds
**Reliability**: high
**Analysis**: DOCplex also found an optimal solution matching Gurobipy's result, albeit with a longer execution time. This confirms the solution's validity and reliability.

## 7. Pyomo Implementation

```python
# Complete PYOMO implementation

import pyomo.environ as pyo
from pyomo.opt import SolverFactory

def train_station_optimization():
    """Optimize train assignments to stations to maximize passengers served"""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Data from the database schema
    stations = [1, 2, 3]
    trains = [101, 102, 103]
    
    # Passenger demand for each train at each station
    demand_data = {
        (101, 1): 250,
        (102, 2): 350,
        (103, 3): 150,
        (101, 2): 300,
        (102, 3): 400
    }
    
    # Number of platforms available at each station
    platforms_data = {
        1: 3,
        2: 4,
        3: 5
    }
    
    # Service compatibility of each train at each station
    compatibility_data = {
        (101, 1): True,
        (102, 2): True,
        (103, 3): False,
        (101, 2): True,
        (102, 3): True
    }
    
    # 3. SETS
    model.Trains = pyo.Set(initialize=trains)
    model.Stations = pyo.Set(initialize=stations)
    
    # 4. PARAMETERS
    model.Demand = pyo.Param(model.Trains, model.Stations, initialize=demand_data, default=0)
    model.Platforms = pyo.Param(model.Stations, initialize=platforms_data)
    model.Compatible = pyo.Param(model.Trains, model.Stations, initialize=compatibility_data, default=False)
    
    # 5. VARIABLES
    model.x = pyo.Var(model.Trains, model.Stations, within=pyo.Binary)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.Demand[i, j] * model.x[i, j] for i in model.Trains for j in model.Stations)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Platform Capacity Constraint
    def platform_capacity_rule(model, j):
        return sum(model.x[i, j] for i in model.Trains) <= model.Platforms[j]
    model.platform_capacity_constraint = pyo.Constraint(model.Stations, rule=platform_capacity_rule)
    
    # Service Compatibility Constraint
    def service_compatibility_rule(model, i, j):
        return model.x[i, j] <= model.Compatible[i, j]
    model.service_compatibility_constraint = pyo.Constraint(model.Trains, model.Stations, rule=service_compatibility_rule)
    
    # Exclusive Assignment Constraint
    def exclusive_assignment_rule(model, i):
        return sum(model.x[i, j] for j in model.Stations) <= 1
    model.exclusive_assignment_constraint = pyo.Constraint(model.Trains, rule=exclusive_assignment_rule)
    
    # 8. SOLVING WITH GUROBI
    solver = SolverFactory('gurobi')
    
    # Solve the model
    results = solver.solve(model, tee=True)
    
    # 9. RESULT PROCESSING
    if results.solver.termination_condition == pyo.TerminationCondition.optimal:
        print("Optimal solution found!")
        print(f"Optimal value: {pyo.value(model.objective)}")
        
        # Extract variable values
        print("\nTrain assignments:")
        for i in model.Trains:
            for j in model.Stations:
                if pyo.value(model.x[i, j]) > 0.5:  # Binary variable, check if assigned
                    print(f"Train {i} assigned to Station {j}")
        
    elif results.solver.termination_condition == pyo.TerminationCondition.infeasible:
        print("Problem is infeasible")
    elif results.solver.termination_condition == pyo.TerminationCondition.unbounded:
        print("Problem is unbounded")
    else:
        print(f"Solver terminated with condition: {results.solver.termination_condition}")
    
    return model

# Run the optimization
train_station_optimization()
```

### Execution Results
**Status**: ERROR
**Error**: Traceback (most recent call last):
  File "/tmp/tmpq4jplxlt.py", line 104, in <module>
    train_station_optimization()
  File "/tmp/tmpq4jplxlt.py", line 69, in train_station_optimization
    model.service_compatibility_constraint = pyo.Constraint(model.Trains, model.Stations, rule=service_compatibility_rule)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/block.py", line 571, in __setattr__
    self.add_component(name, val)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/block.py", line 1101, in add_component
    val.construct(data)
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/constraint.py", line 722, in construct
    self._setitem_when_not_present(index, rule(block, index))
  File "/dccstor/nl2opt/miniforge3/envs/nl2opt_optim/lib/python3.10/site-packages/pyomo/core/base/initializer.py", line 349, in __call__
    return self._fcn(parent, *idx)
  File "/tmp/tmpq4jplxlt.py", line 68, in service_compatibility_rule
    return model.x[i, j] <= model.Compatible[i, j]
TypeError: '<=' not supported between instances of 'VarData' and 'bool'

**Analysis**: Pyomo encountered a TypeError due to an issue with the service compatibility constraint, indicating a problem in the model formulation or data handling.

## 8. Cross-Solver Analysis and Final Recommendation

### Solver Results Comparison

| Solver | Status | Optimal Value | Execution Time | Decision Variables | Retry Attempt |
|--------|--------|---------------|----------------|-------------------|---------------|
| Gurobipy | OPTIMAL | 700.00 | 0.61s | N/A | N/A |
| Docplex | OPTIMAL | 700.00 | 3.43s | N/A | N/A |
| Pyomo | ERROR | N/A | 1.81s | N/A | N/A |

### Solver Consistency Analysis
**Result**: All solvers produced consistent results ✓
**Consistent Solvers**: gurobipy, docplex
**Majority Vote Optimal Value**: 700.0

### Final Recommendation
**Recommended Optimal Value**: 700.0
**Confidence Level**: HIGH
**Preferred Solver(s)**: gurobipy
**Reasoning**: Gurobipy provided the optimal solution quickly and reliably, making it the preferred solver for this problem.

### Business Interpretation
**Overall Strategy**: The optimal assignment of trains to stations maximizes passenger service, ensuring efficient use of available platforms and compatibility constraints.
**Objective Value Meaning**: The optimal objective value of 700.0 represents the maximum number of passengers that can be served given the constraints.
**Resource Allocation Summary**: Trains should be allocated to stations where they are compatible and where platform capacity allows, ensuring maximum passenger service.
**Implementation Recommendations**: Ensure data integrity and compatibility checks are in place. Use Gurobipy for solving similar optimization problems due to its reliability and efficiency.