
\[ \mathrm{min}_{\mathbf{x}} f(\mathbf{x}) + \lambda g(\mathbf{x}) \]
Let \(p^{\star}\) be the solution of original problem: \[ p^{\star} = \mathrm{min}_{\mathbf{x}} f(\mathbf{x}),\quad g(\mathbf{x})\leq 0 \]
Let \(\mathbf{x}^{\star}\) be the value of the decision variables at the minimum \[ p^{\star} = f(\mathbf{x}^{\star}) \geq \cdots\\ f(\mathbf{x}^{\star}) + \lambda g(\mathbf{x}^{\star}) \geq f_d(\lambda) \]
Lagrange dual for any value of \(\lambda\) is a lower bound on original (aka primal) problem







prob.solution.dual_vars\[ \max_{\mathbf{c}} \left(\underbrace{\mathbf{p}_{\mathrm{prod}}^T Y\mathbf{c}}_{\mathrm{profit}} -\underbrace{\left(\mathbf{p}_{\mathrm{proc}}^T+\mathbf{p}_{\mathrm{crude}}^T\right)\mathbf{c}}_{\mathrm{costs}} \right) \\ 0\preceq\mathbf{c}\preceq \mathbf{c}_{\mathrm{max}},\quad \mathrm{crude\,\, capacity} \\ 0 \preceq Y\mathbf{c} \preceq \mathbf{L}_{\mathrm{prod}} \]
| capacity | price | |
|---|---|---|
| gasoline | 24000 | 108 |
| kerosine | 2000 | 72 |
| fuel oil | 6000 | 63 |
| residual | 2500 | 30 |
| available | price | process_cost | |
|---|---|---|---|
| crude 1 | 28000.0 | 72.0 | 1.5 |
| crude 2 | 15000.0 | 45.0 | 3.0 |
| gasoline | kerosine | fuel oil | residual | |
|---|---|---|---|---|
| crude 1 | 80 | 5 | 10 | 5 |
| crude 2 | 44 | 10 | 36 | 10 |
# decision variables, this is a vector of length 2
x = cvx.Variable(len(crudes.index), pos=True, name="crudes")
# the objective will be the revenue minus costs
# The production is the amount of each crude times the yield matrix
prods = yields.to_numpy().T @ x / 100.0
# To find revenue we sum up the production times the price of each crude
revenue = products["price"].to_numpy().T @ prods
# Feed costs are the crude prices times the decision variable, summed up
feed_cost = crudes["price"].to_numpy().T @ x
# Ditto for the process costs
process_cost = crudes["process_cost"].to_numpy().T @ x
# profit
profit = revenue - feed_cost - process_cost
objective = cvx.Maximize(profit)
# constraints: can't use more crude than you have
feeds = x <= crudes["available"].to_numpy()
# Can't produce more of each product than the capacity
capacity = prods <= products["capacity"].to_numpy()
# put them together to get all the constraints
constraints = [feeds, capacity]
# solve using cvx
problem = cvx.Problem(objective, constraints)
problem.solve()860275.8618582088
| available | price | process_cost | consumption | shadow price | |
|---|---|---|---|---|---|
| crude 1 | 28000.0 | 72.0 | 1.5 | 26206.9 | 0.0 |
| crude 2 | 15000.0 | 45.0 | 3.0 | 6896.6 | 0.0 |
| capacity | price | production | unused capacity | shadow price | |
|---|---|---|---|---|---|
| gasoline | 24000 | 108 | 24000.0 | 0.0 | 14.0 |
| kerosine | 2000 | 72 | 2000.0 | 0.0 | 262.6 |
| fuel oil | 6000 | 63 | 5103.4 | 896.6 | 0.0 |
| residual | 2500 | 30 | 2000.0 | 500.0 | 0.0 |

| available | price | process_cost | consumption | shadow price | |
|---|---|---|---|---|---|
| crude 1 | 28000.0 | 72.0 | 1.5 | 24590.2 | 0.0 |
| crude 2 | 15000.0 | 45.0 | 3.0 | 9836.1 | 0.0 |
| capacity | price | production | unused capacity | shadow price | |
|---|---|---|---|---|---|
| gasoline | 24000 | 108 | 24000.0 | -0.0 | 22.6 |
| kerosine | 4000 | 72 | 2213.1 | 1786.9 | 0.0 |
| fuel oil | 6000 | 63 | 6000.0 | -0.0 | 62.4 |
| residual | 2500 | 30 | 2213.1 | 286.9 | 0.0 |
Total Profit: 916229.508223

crudes = pd.DataFrame(
{
"crude 1": {"available": 0, "price": 72, "process_cost": 1.5},
"crude 2": {"available": 15000, "price": 45, "process_cost": 3},
}
).T
# constraints
feeds = x <= crudes["available"].to_numpy()
capacity = prods <= products["capacity"].to_numpy()
constraints = [feeds, capacity]
# solution
problem = cvx.Problem(objective, constraints)
problem.solve()486000.0001304866
| available | price | process_cost | consumption | shadow price | |
|---|---|---|---|---|---|
| crude 1 | 0.0 | 72.0 | 1.5 | 0.0 | 26.6 |
| crude 2 | 15000.0 | 45.0 | 3.0 | 15000.0 | 32.4 |
| capacity | price | production | unused capacity | shadow price | |
|---|---|---|---|---|---|
| gasoline | 24000 | 108 | 6600.0 | 17400.0 | 0.0 |
| kerosine | 4000 | 72 | 1500.0 | 2500.0 | 0.0 |
| fuel oil | 6000 | 63 | 5400.0 | 600.0 | 0.0 |
| residual | 2500 | 30 | 1500.0 | 1000.0 | 0.0 |
6.8994302405939854e-09
# Initialize products data frame with decline
products["obj_loss"] = 0.0
# Start with a loop over each crude product
for product in products.index:
# Make a deep copy of the data
# This lets us change elements without
# overwriting the original data
products_2 = products.copy()
# Now we are going to reduce the capacity by 80%
products_2["capacity"].at[product] = 0.2*products["capacity"].at[product]
# We are ready to solve the optimization problem
feeds = x <= crudes["available"].to_numpy()
capacity = prods <= products_2["capacity"].to_numpy()
constraints = [feeds, capacity]
# solution
problem = cvx.Problem(objective, constraints)
obj = problem.solve()
# Now we compute the relative decrease and save it
products["obj_loss"].at[product] = obj/original_objgasoline 0.410862
kerosine 0.225974
fuel oil 0.338961
residual 0.282468
Name: obj_loss, dtype: float64
In terms of Convex Conjugate \[ \max_{(\lambda,\nu)}-\lambda^T\mathbf{b} -\nu^T\mathbf{d} - f^*(-A^T\lambda-C^T\nu) \\ \lambda \succeq 0 \\ \]
So if we can compute convex conjugate of objective, can actually write dual problem