Lecture 12: Duality#

Note

In this lecture, we will explore the concept of duality in linear programming, where each linear programming problem (called the primal problem) has a corresponding dual problem. The solutions of these problems provide valuable insights into sensitivity analysis.


Overview#

Matrix Form#

For the given linear optimisation problem (referred to as the primal problem),

Objective:

\[ \min_{\mathbf{x}} \ f(\mathbf{x}) = \mathbf{c}^T\mathbf{x} \]

Subject to:

\[\begin{split} \begin{aligned} \mathbf{A}\mathbf{x} & \geq \mathbf{b} \\ \mathbf{x} & \geq 0 \end{aligned} \end{split}\]

There exists a dual problem such that

Objective:

\[ \max_{\mathbf{x}} \ f(\mathbf{y}) = \mathbf{b}^T\mathbf{y} \]

Subject to:

\[\begin{split} \begin{aligned} \mathbf{A}^T\mathbf{y} & \leq \mathbf{c} \\ \mathbf{y} & \geq 0 \end{aligned} \end{split}\]

Expanded Form#

Alternatively, for the given linear optimisation problem (referred to as the primal problem),

Objective:

\[ \min_{\mathbf{x}} \ f(\mathbf{x}) = c_1x_1 + c_2x_2 + ... + c_nx_n \]

Subject to:

\[\begin{split} \begin{aligned} a_{11}x_1 + a_{12}x_2 + ... + a_{1n}x_n & \geq b_1 \\ a_{21}x_1 + a_{22}x_2 + ... + a_{2n}x_n & \geq b_2 \\ ... \\ a_{m1}x_1 + a_{m2}x_2 + ... + a_{mn}x_n & \geq b_m \\ x_i & \geq 0 \ \forall \ i \in [1,n] \end{aligned} \end{split}\]

There exists a dual problem such that

Objective:

\[ \max_{\mathbf{x}} \ f(\mathbf{x}) = b_1y_1 + b_2y_2 + ... + b_my_m \]

Subject to:

\[\begin{split} \begin{aligned} a_{11}y_1 + a_{21}y_2 + ... + a_{m1}y_m & \leq c_1 \\ a_{12}y_1 + a_{22}y_2 + ... + a_{m2}y_m & \leq c_2 \\ ... \\ a_{1n}y_1 + a_{2n}y_2 + ... + a_{mn}y_m & \leq c_m \\ y_i & \geq 0 \ \forall \ i \in [1,m] \end{aligned} \end{split}\]

Note

  • If the primal is a minimization problem, then dual is a maximization problem, and vice versa.

  • The number of decision variables in the dual corresponds to the number of constraints in the primal, and vice versa.

  • The coefficients in the objective function of the dual are derived from the constraint limits of the primal, and vice versa.


Interpreting the Primal and Dual Problems#

The primal problem represents the original optimization perspective, focusing on the objective function through decision variables \((x_i)\). Thus, optimising the primal problem renders primal variable values that optimise the objective function.

The dual problem, on the other hand, provides a complementary perspective by focusing on the constraints themselves, interpreting them as resources with associated dual variables \((y_i)\). Thus, optimising the dual problem renders dual variable values that indicate how much the objective function would improve if a constraint is relaxed by one unit - Shadow Price.

The relationship between the primal and dual problems thus reveals deep economic insights: while the primal focuses on decision-making, the dual evaluates the worth of resources.

Example #1#

Consider a textile firm operating in Kochi. This company needs to ship 100 tons of textile goods from Kanchipuram and can rent two types of trucks - \(\text{T}_1\) and \(\text{T}_2\). Each truck of type \(\text{T}_1\) can carry 10 tons of goods and costs ₹5000 per trip, while each truck of type \(\text{T}_2\) can carry 20 tons and costs ₹8000 per trip. Considering the managerial capacity of the firm (warehouse capacity, staff numbers, etc.), the management committee has imposed an upper limit on truck rental of 20 trucks in total, with a maximum of 12 any individual type of truck. Thus, all things considered, how many \(\text{T}_1\) and \(\text{T}_2\) type trucks should the company deploy so as to minimize the total costs?

For the above problem, develop the primal and dual optimisation problem.

Primal Problem#

Objective:

\[ \min_{\mathbf{x}} \ z = 5000x_1 + 8000x_2 \]

Subject to:

\[\begin{split} \begin{aligned} 10x_1 + 20x_2 & \geq 100 \\ - x_1 - x_2 & \geq - 20 \\ - x_1 & \geq - 12 \\ - x_2 & \geq - 12 \\ x_1, x_2 & \in \mathbb{Z}_+ \end{aligned} \end{split}\]

Here, \(x_1\) and \(x_2\) represent the number of \(\text{T}_1\) and \(\text{T}_2\) type trucks, respectively.

Dual Problem#

Objective:

\[ \max_{\mathbf{y}} \ z = 100y_1 - 20y_2 - 12y_3 - 12y_4 \]

Subject to:

\[\begin{split} \begin{aligned} 10y_1 - y_2 - y_3 & \leq 5000 \\ 20y_1 - y_2 - y_4 & \leq 8000 \\ y_1, y_2, y_3, y_4 & \geq 0 \end{aligned} \end{split}\]

Here, \(y_1\) represents the change in objective function value by changing the shipping demand by 1 ton, while \(y_2\) respresents the change in objective function value by changing the fleet size limit by 1 truck. Similarly, \(y_3\) and \(y_4\) represent the change in objective function value by changing \(\text{T}_1\) and \(\text{T}_2\) type truck fleet size by 1 truck, respectively.

import numpy as np
from scipy.optimize import linprog

# Cost Parameter
c = [5000, 8000]

# Constraint Parameter (Coefficients)
a = [[10, 20], [-1, -1], [-1, 0], [0, -1]]

# Constraint Parameter (Limits)
b = [249, -20, -12, -12]

# Primal Domain Constraints
x1_bounds = (0, None)
x2_bounds = (0, None)
bounds = [x1_bounds, x2_bounds]
integrality = [0, 0]  # Relaxed integer constraint

# Solve Primal
result = linprog(np.array(c), A_ub=-np.array(a), b_ub=-np.array(b), bounds=bounds, integrality=integrality, method='highs')
print(f"Optimal number of T1 trucks: {result.x[0]}, T2 trucks: {result.x[1]}; Minimum cost: {result.fun}")

# Dual Domain Constraints
y1_bounds = (0, None)
y2_bounds = (0, None)
y3_bounds = (0, None)
y4_bounds = (0, None)
bounds = [y1_bounds, y2_bounds, y3_bounds, y4_bounds]
integrality = [0, 0, 0, 0]

# Solve Dual
result = linprog(-np.transpose(b), A_ub=np.transpose(a), b_ub=np.array(c), bounds=bounds, integrality=integrality, method='highs')
print(f"Shadow price for shipment demand: {result.x[0]}, total fleet size limit: {result.x[1]}, T1 fleet size limit: {result.x[2]}, T2 fleet size limit: {result.x[3]}")
Optimal number of T1 trucks: 0.9, T2 trucks: 12.0; Minimum cost: 100500.0
Shadow price for shipment demand: 500.0, total fleet size limit: 0.0, T1 fleet size limit: 0.0, T2 fleet size limit: 2000.0

Solve the primal and dual problem, and interpret the outcomes,

  1. for the given constraint limits \((b_i)\)

  2. reducing shipment demand \((b_1)\) by 1 ton

  3. increase shipment demand \((b_1)\) to 250 ton

  4. reducing shipment demand \((b_1)\) by 1 ton

Caution

For the purpose of discussion, we have relaxed the integer constraints of the primal problem in the solution below.

SNo.

\(x_1\)

\(x_2\)

\(Z\)

\(y_1\)

\(y_2\)

\(y_3\)

\(y_4\)

1

0

5.00

40.0k

400

0

0

0

2

0

4.95

39.6k

400

0

0

0

3

1

12.0

101.0k

500

0

0

2000

4

0.9

12.0

100.5k

500

0

0

2000

Example #2#

Consider the two routes, \(\text{R}_1\) and \(\text{R}_2\), connecting Chennai Central Railway Station to Madras International Meenambakkam Airport. Here, \(\text{R}_1\) is a major arterial route with a travel time of 30 minutes and capacity of 1000 vehicles per hour. On the other hand, \(\text{R}_2\) is a collector route with a travel time of 45 minutes and capacity of 750 vehicles per hour. Assuming that Chennai Unified Metropolitan Transport Authority (CUMTA) has approporiate traffic control systems to route traffic through either of these routes, how should CUMTA allocate peak traffic of 1200 vehicles per hour to each route in order to minimise the total vehicle hours traveled (VHT)?

For the above problem develop and solve the primal and dual problem, and interpret the outcomes.

Primal Problem#

Objective:

\[ \min_{\mathbf{x}} \ z = 30x_1 + 45x_2 \]

Subject to:

\[\begin{split} \begin{aligned} x_1 + x_2 & \geq 1200 \\ - x_1 & \geq - 1000 \\ - x_2 & \geq - 750 \\ x_1, x_2 & \in \mathbb{Z}_+ \end{aligned} \end{split}\]

Here, \(x_1\) and \(x_2\) represent the number of vehicles on route \(\text{R}_1\) and \(\text{R}_2\), respectively.

Dual Problem#

Objective:

\[ \min_{\mathbf{y}} \ z = 1200y_1 - 1000y_2 - 750y_3 \]

Subject to:

\[\begin{split} \begin{aligned} y_1 - y_2 & \leq 30 \\ y_1 - y_3 & \leq 45 \\ y_1, y_2, y_3 & \geq 0 \end{aligned} \end{split}\]

Here, \(y_1\), \(y_2\), and \(y_3\) represent the change in objective function value by changing peak traffic demand, \(\text{R}_1\) capacity, and \(\text{R}_2\) capacity, by 1 vehicle/hour, respectively.

import numpy as np
from scipy.optimize import linprog

# Cost Parameter
c = [30, 45]

# Constraint Parameter (Coefficients)
a = [[1, 1],[-1, 0], [0, -1]]

# Constraint Parameter (Limits)
b = [1200, -1000, -750]

# Primal Domain Constraints
x1_bounds = (0, None)
x2_bounds = (0, None)
bounds = [x1_bounds, x2_bounds]
integrality = [0, 0]

# Solve Primal
result = linprog(np.array(c), A_ub=-np.array(a), b_ub=-np.array(b), bounds=bounds, integrality=integrality, method='highs')
print(f"Optimal number of vehicles on R1: {result.x[0]}, R2: {result.x[1]}; Minimum cost: {result.fun}")

# Dual Domain Constraints
y1_bounds = (0, None)
y2_bounds = (0, None)
y3_bounds = (0, None)
bounds = [y1_bounds, y2_bounds, y3_bounds]
integrality = [0, 0, 0]

# Solve Dual
result = linprog(-np.transpose(b), A_ub=np.transpose(a), b_ub=np.array(c), bounds=bounds, integrality=integrality, method='highs')
print(f"Shadow price for peak demand: {result.x[0]}, R1 capacity: {result.x[1]}, R2 capactiy: {result.x[2]}")
Optimal number of vehicles on R1: 1000.0, R2: 200.0; Minimum cost: 39000.0
Shadow price for peak demand: 45.0, R1 capacity: 15.0, R2 capactiy: 0.0

Caution

For the purpose of discussion, we have relaxed the integer constraints of the primal problem in the solution above.

Example #3#

The Southern Railways wants to start a new daily Vande-Bharat (VB) service between Chennai and Rameshwaram. However, the daily passenger demand is stochastic (uncertain) and follows a normal distribution – with a mean of 900 and standard deviation of 100 for the economy class, and a mean of 30 and standard deviation of 10 for the executive class. Note, the standard 78-capacity economy class coach amounts to ₹50,000 in daily operational costs and generates an average revenue of ₹750 per seat. On the other hand, the standard 52-capacity executive class coach amounts to ₹55,000 in daily operational costs and generates an average revenue of ₹2000 per seat. Assuming that a minimum of 16 coaches must be deployed, how many economy and executive coaches should the Southern Railways deploy in the VB so as to maximize the profits while maintaining a 95% service level (satisfying passenger demand on 95% of the days)?

For the above problem develop and solve the primal and dual problem, and interpret the outcomes.

Primal Problem#

Objective:

\[ \min_{\mathbf{x}} \ z = 50000x_1 + 55000x_2 \]

Subject to:

\[\begin{split} \begin{aligned} 78x_1 & \geq 1064.5 \\ 52x_2 & \geq 46.4 \\ x_1 + x_2 & \geq 16 \\ x_1, x_2 & \in \mathbb{Z}_+ \end{aligned} \end{split}\]

Here, \(x_1\) and \(x_2\) represent the number of economy- and executive- class coached, respectively.

Dual Problem#

Objective:

\[ \max_{\mathbf{y}} \ z = 1064.5y_1 + 46.4y_2 + 16y_3 \]

Subject to:

\[\begin{split} \begin{aligned} 78y_1 + y_3 & \leq 50000 \\ 52y_2 + y_3 & \leq 55000 \\ y_1, y_2, y_3 & \geq 0 \end{aligned} \end{split}\]

Here, \(y_1\) and \(y_2\) represent the change in objective function value by changing daily economy and executive class demand by 1 passenger, respectively. On the other hand, \(y_3\) represents the change in objective function value by changing minimum number of total coaches required by 1 unit.

import numpy as np
from scipy.optimize import linprog

# Cost Parameter
c = [50000, 55000]

# Constraint Parameter (Coefficients)
a = [[78, 0], [0, 52], [1, 1]]

# Constraint Parameter (Limits)
b = [1064.5, 46.4, 16]

# Domain Constraints
x1_bounds = (0, None)
x2_bounds = (0, None)
bounds = [x1_bounds, x2_bounds]
integrality = [0, 0]  # Relaxed integer constraint

# Solve
result = linprog(np.array(c), A_ub=-np.array(a), b_ub=-np.array(b), bounds=bounds, integrality=integrality, method='highs')
print(f"Optimal number of economy class coach: {round(result.x[0], 3)}, executive class coach: {round(result.x[1], 3)} | Minimum cost: {round(result.fun, 3)} ")

# Dual Domain Constraints
y1_bounds = (0, None)
y2_bounds = (0, None)
y3_bounds = (0, None)
bounds = [y1_bounds, y2_bounds, y3_bounds]
integrality = [0, 0, 0]

# Solve Dual
result = linprog(-np.transpose(b), A_ub=np.transpose(a), b_ub=np.array(c), bounds=bounds, integrality=integrality, method='highs')
print(f"Shadow price for economy class demand: {round(result.x[0], 3)}, executive class demand: {round(result.x[1], 3)}, coach limit: {result.x[2]}")
Optimal number of economy class coach: 15.108, executive class coach: 0.892 | Minimum cost: 804461.538 
Shadow price for economy class demand: 0.0, executive class demand: 96.154, coach limit: 50000.0

Caution

For the purpose of discussion, we have relaxed the integer constraints of the primal problem in the solution above.


Caution

The inference drawn from each problem is only valid holding other parameter values fixed. Changing cost coefficients or constraint coefficients will impact the outcomes for primal and dual variables.