# Problem Description:
A mining company operates three ore processing plants (P1, P2, P3) with monthly processing capacities of 8000, 10000, and 12000 tons respectively. The company sources ore from four different mines (M1, M2, M3, M4) with available quantities of 6000, 9000, 7000, and 5000 tons per month. 

Each ore type has different characteristics:
- Metal recovery rates: M1=85%, M2=92%, M3=78%, M4=88%
- Processing costs per ton: M1=$12, M2=$18, M3=$10, M4=$15
- Sulfur content (impurity): M1=1.2%, M2=0.8%, M3=2.1%, M4=1.5%
- Maximum allowed average sulfur content across all plants: 1.5%

Transportation costs per ton from mines to plants:
|       | P1  | P2  | P3  |
|-------|-----|-----|-----|
|   M1  | $4  | $6  | $5  |
|   M2  | $7  | $5  | $8  |
|   M3  | $3  | $4  | $6  |
|   M4  | $8  | $6  | $7  |

The company needs to determine the optimal ore allocation from mines to plants to maximize total metal recovery while keeping total costs under $500,000 and meeting all operational constraints.

Problem type: LP (Small: 12 variables, 9 constraints, 45 nonzeros)

Mathematical model:
Set:
1. Mines 
The set of mines, \( M = \{M1, M2, M3, M4\} \)
2. Plants
The set of processing plants, \( P = \{P1, P2, P3\} \)

Parameter:
1. MineSupply  
\( s_m \): Available ore at mine m (tons)  
[M1:6000, M2:9000, M3:7000, M4:5000]  
2. PlantCapacity  
\( c_p \): Processing capacity of plant p (tons)  
[P1:8000, P2:10000, P3:12000]  
3. RecoveryRate  
\( r_m \): Metal recovery rate for mine m (%)  
[M1:0.85, M2:0.92, M3:0.78, M4:0.88]  
4. ProcessingCost  
\( pc_m \): Processing cost per ton for mine m ($)  
[M1:12, M2:18, M3:10, M4:15]  
5. SulfurContent  
\( sc_m \): Sulfur content for mine m (%)  
[M1:1.2, M2:0.8, M3:2.1, M4:1.5]  
6. TransportCost  
\( tc_{mp} \): Transportation cost from mine m to plant p ($)  
(As given in the cost matrix)  
7. MaxSulfur  
1.5 (%) Maximum allowed average sulfur content  
8. Budget  
500000 ($) Total cost limit  

Decision variable:
1. Allocation  
\( x_{mp} \geq 0 \): Continuous variable representing tons of ore shipped from mine m to plant p  

Objective:  
Maximize total metal recovery  
max: \( \sum_{m\in M} \sum_{p\in P} r_m \cdot x_{mp} \)

Constraint:
1. Mine supply limit  
\( \sum_{p\in P} x_{mp} \leq s_m, \forall m \in M \)
2. Plant capacity limit  
\( \sum_{m\in M} x_{mp} \leq c_p, \forall p \in P \)
3. Total cost limit (processing + transportation)  
\( \sum_{m\in M} \sum_{p\in P} (pc_m + tc_{mp}) \cdot x_{mp} \leq 500000 \)
4. Average sulfur content restriction  
\( \frac{\sum_{m\in M} \sum_{p\in P} sc_m \cdot x_{mp}}{\sum_{m\in M} \sum_{p\in P} x_{mp}} \leq 1.5 \)
5. Non-negativity  
\( x_{mp} \geq 0, \forall m \in M, p \in P \)

Type:
Continuous, Linear, Linear  
LP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_mining_optimization(
    mines=['M1', 'M2', 'M3', 'M4'],
    plants=['P1', 'P2', 'P3'],
    mine_supply={'M1': 6000, 'M2': 9000, 'M3': 7000, 'M4': 5000},
    plant_capacity={'P1': 8000, 'P2': 10000, 'P3': 12000},
    recovery_rate={'M1': 0.85, 'M2': 0.92, 'M3': 0.78, 'M4': 0.88},
    processing_cost={'M1': 12, 'M2': 18, 'M3': 10, 'M4': 15},
    sulfur_content={'M1': 1.2, 'M2': 0.8, 'M3': 2.1, 'M4': 1.5},
    max_sulfur=1.5,
    budget=500000,
    transport_cost={
        'M1': {'P1': 4, 'P2': 6, 'P3': 5},
        'M2': {'P1': 7, 'P2': 5, 'P3': 8},
        'M3': {'P1': 3, 'P2': 4, 'P3': 6},
        'M4': {'P1': 8, 'P2': 6, 'P3': 7}
    }
):
    """Solve the mining company's ore allocation optimization problem."""
    # Create a new model
    model = gp.Model("MiningAllocation")

    # Decision variables: x_mp = tons shipped from mine m to plant p
    x = model.addVars(mines, plants, name="Allocation", lb=0)

    # Objective: Maximize total metal recovery
    model.setObjective(
        gp.quicksum(recovery_rate[m] * x[m, p] for m in mines for p in plants),
        GRB.MAXIMIZE
    )

    # Constraints
    # 1. Mine supply limit
    model.addConstrs(
        (gp.quicksum(x[m, p] for p in plants) <= mine_supply[m] for m in mines),
        name="MineSupply"
    )

    # 2. Plant capacity limit
    model.addConstrs(
        (gp.quicksum(x[m, p] for m in mines) <= plant_capacity[p] for p in plants),
        name="PlantCapacity"
    )

    # 3. Total cost limit (processing + transportation)
    model.addConstr(
        gp.quicksum((processing_cost[m] + transport_cost[m][p]) * x[m, p]
                   for m in mines for p in plants) <= budget,
        name="TotalCost"
    )

    # 4. Average sulfur content restriction
    total_sulfur = gp.quicksum(sulfur_content[m] * x[m, p] for m in mines for p in plants)
    total_tonnage = gp.quicksum(x[m, p] for m in mines for p in plants)
    model.addConstr(total_sulfur <= max_sulfur * total_tonnage, name="SulfurLimit")

    # Optimize the model
    model.optimize()

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


# Execute the function
if __name__ == "__main__":
    result = solve_mining_optimization()
    print(result)

Solution status: OPTIMAL
Optimal objective value: 22933.33333