CraftBrew Inc. operates two breweries located in Portland and Asheville. The Portland brewery can produce up to 200,000 gallons of craft beer annually, while the Asheville brewery can produce up to 300,000 gallons. Once brewed, the beer is shipped to two distribution hubs: Seattle and Atlanta. Each distribution hub can handle up to 400,000 gallons per year. Due to differences in shipping and production costs, the profit (in dollars) per thousand gallons shipped depends on the brewery and the destination hub (see Table 1). CraftBrew Inc. is considering expanding the capacity of each brewery. Each thousand gallons of annual production capacity added will cost $1,000 for the Portland brewery and $1,500 for the Asheville brewery. How can CraftBrew Inc. maximize its profits, less expansion costs, over a three-year period?  

Table 1:

| Brewery   | Destination Hub | Profit per Thousand Gallons ($) |  
|-----------|------------------|----------------------------------|  
| Portland  | Seattle          | 500                              |  
| Portland  | Atlanta          | 450                              |  
| Asheville | Seattle          | 480                              |  
| Asheville | Atlanta          | 520                              |

Problem type: LP (Small: 18 variables, 12 constraints, 36 nonzeros)

Mathematical model:
Set:
1. Brewery
The set of breweries, \( B = \{1, 2\} \)  # 1: Portland, 2: Asheville
2. DestinationHub
The set of destination hubs, \( H = \{1, 2\} \)  # 1: Seattle, 2: Atlanta
3. Year
The set of years, \( Y = \{1, 2, 3\} \)

Parameter:
1. ProductionCapacity
[200000, 300000]  # in gallons
2. ShippingCapacity
[400000, 400000]  # in gallons
3. ProfitPerThousandGallons
[[500, 450], [480, 520]]  # in dollars per thousand gallons
4. ExpansionCost
[1000, 1500]  # in dollars per thousand gallons

Decision variable:
1. ProductionCapacityAdded
Continous variable, \( ProductionCapacityAdded[b] \forall b \in B \), the amount of production capacity added for brewery \( b \) (in thousands of gallons).
2. ProductionFromBrewery2Hub
Continous variable, \( ProductionFromBrewery2Hub[b][h][y] \forall b \in B, h \in H, y \in Y \), the amount of product shipped from brewery \( b \) to hub \( h \) in year \( y \) (in thousands of gallons).

Objective:
1. Maximize the total profit.
max: \sum_{b \in B, h \in H, y \in Y} (ProductionFromBrewery2Hub[b][h][y] * ProfitPerThousandGallons[b][h]) - \sum_{b \in B} (ProductionCapacityAdded[b] * ExpansionCost[b])

Constraint:
1. Production capacity constraint.
\sum_{h \in H} ProductionFromBrewery2Hub[b][h][y] \leq (ProductionCapacity[b] + ProductionCapacityAdded[b]) \forall b \in B, y \in Y
2. Shipping capacity constraint.
\sum_{b \in B} ProductionFromBrewery2Hub[b][h][y] \leq ShippingCapacity[h] \forall h \in H, y \in Y
3. Non-negativity constraint.
ProductionCapacityAdded[b] \geq 0 \forall b \in B
ProductionFromBrewery2Hub[b][h][y] \geq 0 \forall b \in B, h \in H, y \in Y

Type:
Continuous, Linear, Linear
LP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_brewery_optimization(
    ProductionCapacity=[200, 300],
    ShippingCapacity=[400, 400],
    ProfitPerThousandGallons=[[500, 450], [480, 520]],
    ExpansionCost=[1000, 1500]
):
    """
    Models and solves the brewery production and distribution optimization problem.
    """
    # Create a new model
    model = gp.Model("Brewery_Production_Distribution_Optimization")

    # Sets
    B = range(len(ProductionCapacity))  # Breweries
    H = range(len(ShippingCapacity))  # Destination Hubs
    Y = range(3)  # Years

    # Decision Variables
    ProductionCapacityAdded = model.addVars(B, lb=0.0, name="ProductionCapacityAdded")
    ProductionFromBrewery2Hub = model.addVars(B, H, Y, lb=0.0, name="ProductionFromBrewery2Hub")

    # Objective Function: Maximize total profit
    profit = gp.quicksum(
        ProductionFromBrewery2Hub[b, h, y] * ProfitPerThousandGallons[b][h]
        for b in B for h in H for y in Y
    )
    expansion_costs = gp.quicksum(
        ProductionCapacityAdded[b] * ExpansionCost[b]
        for b in B
    )
    total_profit = profit - expansion_costs

    model.setObjective(total_profit, GRB.MAXIMIZE)

    # Constraints
    # 1. Production capacity constraint
    for b in B:
        for y in Y:
            model.addConstr(
                gp.quicksum(ProductionFromBrewery2Hub[b, h, y] for h in H) <=
                ProductionCapacity[b] + ProductionCapacityAdded[b],
                f"ProductionCapacity_B{b + 1}_Y{y + 1}"
            )

    # 2. Shipping capacity constraint
    for h in H:
        for y in Y:
            model.addConstr(
                gp.quicksum(ProductionFromBrewery2Hub[b, h, y] for b in B) <= ShippingCapacity[h],
                f"ShippingCapacity_H{h + 1}_Y{y + 1}"
            )

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


Solution status: OPTIMAL
Optimal objective value: 903000