You are in charge of GreenTech Manufacturing, a company that produces eco-friendly packaging materials from recycled paper. The company produces two types of packaging: Premium and Standard. Premium packaging sells for $15 per ton and must have an average quality score of at least 9. Standard packaging sells for $8 per ton and must have an average quality score of at least 7. The maximum demand is 2,500 tons of Premium packaging and 800 tons of Standard packaging per month. 

Recycled paper can be processed using one of three methods. The yield and cost per ton for each method are shown in the table below. For example, processing one ton of recycled paper using Method 1 costs $4.20 and yields 0.3 tons of quality score 6, 0.4 tons of quality score 8, and 0.3 tons of quality score 10. These costs include the expenses of acquiring the recycled paper. Before being converted into packaging materials, quality scores 6 and 8 can be enhanced through a refinement process. For $1.20 per ton, one ton of quality score 6 can be upgraded to quality score 8. For $1.80 per ton, one ton of quality score 8 can be upgraded to quality score 10. How can GreenTech Manufacturing maximize its monthly profit while meeting quality and demand requirements?

| Processing Method | Cost per Ton ($) | Yield (Quality 6) | Yield (Quality 8) | Yield (Quality 10) |  
|--------------------|-------------------|--------------------|--------------------|---------------------|  
| Method 1           | 4.20              | 0.3                | 0.4                | 0.3                 |  
| Method 2           | 3.80              | 0.5                | 0.3                | 0.2                 |  
| Method 3           | 4.50              | 0.2                | 0.5                | 0.3                 |

Problem type: LP (Small: 11 variables, 7 constraints, 31 nonzeros)

Mathematical model:
Set:
1. Packaging
The set of packaging types, \( P = \{1, 2\} \)  # 1: Premium, 2: Standard
2. Method
The set of production methods, \( M = \{1, 2, 3\} \)  
3. YieldQuality
The set of quality scores, \( Q = \{1, 2, 3\} \) # 1: Quality 6, 2: Quality 8, 3: Quality 10

Parameter:
1. CostPerTonPerMethod
[4.20, 3.80, 4.50]  # in dollars
2. YieldPerMethod
[[0.3, 0.4, 0.3], [0.5, 0.3, 0.2], [0.2, 0.5, 0.3]]  # yield of quality scores per method
3. UpgradeCost
[1.20, 1.80]  # cost to upgrade quality scores 6 to 8 and 8 to 10 in dollars
4. PricePerTon
[15, 8]  # price per ton for Premium and Standard packaging in dollars
5. MinQualityScore
[9, 7]  # minimum quality score for Premium and Standard packaging
6. MaxDemand
[2500, 800]  # maximum demand for Premium and Standard packaging in tons

Decision variable:
1. ProcessingAmount
Continuous variable, \( ProcessingAmount[m] \forall m \in M \), the amount of recycled paper processed using method \( m \) in tons.
2. Quality6UpgradeAmount
Continuous variable, \( Quality6UpgradeAmount \), the amount of quality score 6 upgraded to quality score 8 in tons.
3. Quality8UpgradeAmount
Continuous variable, \( Quality8UpgradeAmount \), the amount of quality score 8 upgraded to quality score 10 in tons.
4. PackagingProducedPerQuality
Continuous variable, \( PackagingProduced[p][q] \forall p \in P, q \in Q \), the amount of packaging produced of type \( p \) with quality score \( q \) in tons.

Objective:
1. Maximize the total profit.
max: \sum_{p \in P, q \in Q} (PricePerTon[p] * PackagingProduced[p][q]) - \sum_{m \in M} (CostPerTonPerMethod[m] * ProcessingAmount[m]) - UpgradeCost[0] * Quality6UpgradeAmount - UpgradeCost[1] * Quality8UpgradeAmount

Constraint:
1. Quality score constraint for packaging.
\sum_{q \in Q} (q * PackagingProduced[p][q]) \geq MinQualityScore[p] * \sum_{q \in Q} PackagingProduced[p][q] \forall p \in P
2. Demand constraint for packaging.
\sum_{q \in Q} PackagingProduced[p][q] \leq MaxDemand[p] \forall p \in P
3. Yield constraint for quality scores.
\sum_{m \in M} (YieldPerMethod[m][0] * ProcessingAmount[m]) - Quality6UpgradeAmount \geq \sum_{p \in P} PackagingProduced[p][1] \quad \text{(Quality 6)}
\sum_{m \in M} (YieldPerMethod[m][1] * ProcessingAmount[m]) + Quality6UpgradeAmount - Quality8UpgradeAmount \geq \sum_{p \in P} PackagingProduced[p][2] \quad \text{(Quality 8)}
\sum_{m \in M} (YieldPerMethod[m][2] * ProcessingAmount[m]) + Quality8UpgradeAmount \geq \sum_{p \in P} PackagingProduced[p][3] \quad \text{(Quality 10)}
4. Non-negativity constraint.
ProcessingAmount[m] \geq 0 \forall m \in M
PackagingProduced[p][q] \geq 0 \forall p \in P, q \in Q
Quality6UpgradeAmount \geq 0
Quality8UpgradeAmount \geq 0

Type:
Continuous, Linear, Linear
LP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_greentech_manufacturing(
    cost_per_ton_method={"Method1": 4.20, "Method2": 3.80, "Method3": 4.50},
    yield_per_method={
        "Method1": {"Q6": 0.3, "Q8": 0.4, "Q10": 0.3},
        "Method2": {"Q6": 0.5, "Q8": 0.3, "Q10": 0.2},
        "Method3": {"Q6": 0.2, "Q8": 0.5, "Q10": 0.3}
    },
    actual_quality_values={"Q6": 6, "Q8": 8, "Q10": 10},
    upgrade_cost={"Q6_to_Q8": 1.20, "Q8_to_Q10": 1.80},
    price_per_ton={"Premium": 15, "Standard": 8},
    min_quality_score_req={"Premium": 9, "Standard": 7},
    max_demand={"Premium": 2500, "Standard": 800}
):
    """
    Models and solves the GreenTech Manufacturing problem.
    """
    # Create a new model
    model = gp.Model("GreenTechManufacturing")

    # --- Sets ---
    packaging_types = ["Premium", "Standard"]
    methods = ["Method1", "Method2", "Method3"]
    quality_labels = ["Q6", "Q8", "Q10"]

    # --- Decision Variables ---
    processing_amount = model.addVars(methods, name="ProcessingAmount", lb=0)
    quality6_upgrade_amount = model.addVar(name="Quality6UpgradeAmount", lb=0)
    quality8_upgrade_amount = model.addVar(name="Quality8UpgradeAmount", lb=0)
    packaging_produced = model.addVars(packaging_types, quality_labels, name="PackagingProduced", lb=0)

    # --- Objective Function: Maximize Total Profit ---
    total_revenue = gp.quicksum(price_per_ton[p] * packaging_produced.sum(p, '*') for p in packaging_types)
    total_processing_cost = gp.quicksum(cost_per_ton_method[m] * processing_amount[m] for m in methods)
    total_upgrade_cost = (upgrade_cost["Q6_to_Q8"] * quality6_upgrade_amount +
                          upgrade_cost["Q8_to_Q10"] * quality8_upgrade_amount)
    model.setObjective(total_revenue - total_processing_cost - total_upgrade_cost, GRB.MAXIMIZE)

    # --- Constraints ---
    for p in packaging_types:
        weighted_quality_sum = gp.quicksum(actual_quality_values[q_label] * packaging_produced[p, q_label]
                                           for q_label in quality_labels)
        total_produced_p = packaging_produced.sum(p, '*')
        model.addConstr(weighted_quality_sum >= min_quality_score_req[p] * total_produced_p,
                        name=f"QualityScore_{p}")

    for p in packaging_types:
        model.addConstr(packaging_produced.sum(p, '*') <= max_demand[p], name=f"Demand_{p}")

    produced_q6_material = gp.quicksum(yield_per_method[m]["Q6"] * processing_amount[m] for m in methods)
    used_as_q6_in_packaging = gp.quicksum(packaging_produced[p, "Q6"] for p in packaging_types)
    model.addConstr(produced_q6_material - quality6_upgrade_amount >= used_as_q6_in_packaging, name="Balance_Q6")

    produced_q8_material_native = gp.quicksum(yield_per_method[m]["Q8"] * processing_amount[m] for m in methods)
    total_available_q8_material = produced_q8_material_native + quality6_upgrade_amount
    used_as_q8_in_packaging = gp.quicksum(packaging_produced[p, "Q8"] for p in packaging_types)
    model.addConstr(total_available_q8_material - quality8_upgrade_amount >= used_as_q8_in_packaging, name="Balance_Q8")

    produced_q10_material_native = gp.quicksum(yield_per_method[m]["Q10"] * processing_amount[m] for m in methods)
    total_available_q10_material = produced_q10_material_native + quality8_upgrade_amount
    used_as_q10_in_packaging = gp.quicksum(packaging_produced[p, "Q10"] for p in packaging_types)
    model.addConstr(total_available_q10_material >= used_as_q10_in_packaging, name="Balance_Q10")

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


Solution status: OPTIMAL
Optimal objective value: 28864