Path: blob/main/latex-templates/templates/operations-research/linear_programming.tex
75 views
unlisted
% Linear Programming Analysis Template1% Topics: Simplex algorithm, duality theory, sensitivity analysis, graphical solutions2% Style: Operations research technical report with computational implementation34\documentclass[a4paper, 11pt]{article}5\usepackage[utf8]{inputenc}6\usepackage[T1]{fontenc}7\usepackage{amsmath, amssymb}8\usepackage{graphicx}9\usepackage{siunitx}10\usepackage{booktabs}11\usepackage{subcaption}12\usepackage[makestderr]{pythontex}1314% Theorem environments15\newtheorem{definition}{Definition}[section]16\newtheorem{theorem}{Theorem}[section]17\newtheorem{example}{Example}[section]18\newtheorem{remark}{Remark}[section]1920\title{Linear Programming: Simplex Method, Duality, and Sensitivity Analysis}21\author{Operations Research Laboratory}22\date{\today}2324\begin{document}25\maketitle2627\begin{abstract}28This technical report presents a comprehensive analysis of linear programming (LP) techniques,29including graphical solutions for two-variable problems, the simplex algorithm for30higher-dimensional optimization, duality theory, and sensitivity analysis. We examine a31production planning problem, demonstrate the simplex tableau method, derive the dual problem,32and analyze shadow prices and reduced costs. Computational implementation using Python's33\texttt{scipy.optimize.linprog} validates theoretical results and illustrates practical34applications in resource allocation.35\end{abstract}3637\section{Introduction}3839Linear programming (LP) is a fundamental technique in operations research for optimizing40a linear objective function subject to linear equality and inequality constraints. Since41George Dantzig's development of the simplex algorithm in 1947, LP has become one of the42most widely applied optimization methods in industry, logistics, finance, and engineering.4344\begin{definition}[Linear Programming Problem]45A linear programming problem in standard form seeks to:46\begin{equation}47\begin{aligned}48\text{maximize} \quad & \mathbf{c}^T \mathbf{x} \\49\text{subject to} \quad & \mathbf{A}\mathbf{x} \leq \mathbf{b} \\50& \mathbf{x} \geq \mathbf{0}51\end{aligned}52\end{equation}53where $\mathbf{c} \in \mathbb{R}^n$ is the objective coefficient vector, $\mathbf{A} \in \mathbb{R}^{m \times n}$54is the constraint matrix, $\mathbf{b} \in \mathbb{R}^m$ is the right-hand side vector, and55$\mathbf{x} \in \mathbb{R}^n$ is the decision variable vector.56\end{definition}5758\section{Theoretical Framework}5960\subsection{Fundamental Concepts}6162\begin{definition}[Feasible Region]63The feasible region $\mathcal{F}$ is the set of all points satisfying the constraints:64\begin{equation}65\mathcal{F} = \{\mathbf{x} \in \mathbb{R}^n : \mathbf{A}\mathbf{x} \leq \mathbf{b}, \mathbf{x} \geq \mathbf{0}\}66\end{equation}67\end{definition}6869\begin{theorem}[Fundamental Theorem of Linear Programming]70If a linear programming problem has an optimal solution, then at least one optimal solution71occurs at an extreme point (vertex) of the feasible region.72\end{theorem}7374\begin{definition}[Basic Feasible Solution]75A basic feasible solution (BFS) is a feasible solution where at most $m$ variables (out of $n$)76are non-zero. These non-zero variables are called \textit{basic variables}, and the zero77variables are \textit{non-basic variables}.78\end{definition}7980\subsection{The Simplex Algorithm}8182The simplex algorithm moves from one BFS to an adjacent BFS with a better objective value,83continuing until optimality is reached or unboundedness is detected.8485\begin{theorem}[Optimality Criterion]86A basic feasible solution is optimal if all reduced costs $\bar{c}_j = c_j - \mathbf{c}_B^T \mathbf{A}_B^{-1} \mathbf{A}_j$87are non-positive (for maximization) or non-negative (for minimization).88\end{theorem}8990\subsection{Duality Theory}9192\begin{definition}[Dual Problem]93For the primal LP problem, the dual problem is:94\begin{equation}95\begin{aligned}96\text{minimize} \quad & \mathbf{b}^T \mathbf{y} \\97\text{subject to} \quad & \mathbf{A}^T\mathbf{y} \geq \mathbf{c} \\98& \mathbf{y} \geq \mathbf{0}99\end{aligned}100\end{equation}101where $\mathbf{y} \in \mathbb{R}^m$ are the dual variables (shadow prices).102\end{definition}103104\begin{theorem}[Strong Duality]105If the primal problem has an optimal solution $\mathbf{x}^*$ with objective value $z^*$,106then the dual problem has an optimal solution $\mathbf{y}^*$ with objective value $w^*$,107and $z^* = w^*$.108\end{theorem}109110\begin{theorem}[Complementary Slackness]111At optimality, for each constraint $i$:112\begin{equation}113y_i^* \left(b_i - \sum_{j=1}^n a_{ij}x_j^* \right) = 0114\end{equation}115and for each variable $j$:116\begin{equation}117x_j^* \left(\sum_{i=1}^m a_{ij}y_i^* - c_j \right) = 0118\end{equation}119\end{definition}120121\section{Computational Analysis}122123\subsection{Problem Formulation: Production Planning}124125We consider a furniture manufacturer producing tables and chairs with limited resources.126Each table requires 4 hours of labor and 3 board-feet of wood, yielding a profit of \$30.127Each chair requires 2 hours of labor and 2 board-feet of wood, yielding a profit of \$20.128Available resources are 80 hours of labor and 60 board-feet of wood. The goal is to maximize profit.129130This translates to the following LP formulation:131\begin{equation}132\begin{aligned}133\text{maximize} \quad & z = 30x_1 + 20x_2 \\134\text{subject to} \quad & 4x_1 + 2x_2 \leq 80 \quad \text{(labor)} \\135& 3x_1 + 2x_2 \leq 60 \quad \text{(wood)} \\136& x_1, x_2 \geq 0137\end{aligned}138\end{equation}139where $x_1$ is the number of tables and $x_2$ is the number of chairs.140141\begin{pycode}142import numpy as np143import matplotlib.pyplot as plt144from scipy.optimize import linprog145from matplotlib.patches import Polygon146147np.random.seed(42)148149# Problem parameters - Production Planning150c = np.array([30, 20]) # Objective coefficients (profit per unit)151A = np.array([152[4, 2], # Labor constraint153[3, 2] # Wood constraint154])155b = np.array([80, 60]) # Resource availability156157# Solve using linprog (minimization, so negate c)158result = linprog(-c, A_ub=A, b_ub=b, bounds=[(0, None), (0, None)], method='highs')159x_optimal = result.x160z_optimal = -result.fun161shadow_prices = result.ineqlin.marginals162reduced_costs = result.lower.marginals163164# Calculate vertices of feasible region165vertices = []166vertices.append([0, 0]) # Origin167168# Intersection with axes169vertices.append([min(b[0]/A[0,0], b[1]/A[1,0]), 0]) # x1-axis170vertices.append([0, min(b[0]/A[0,1], b[1]/A[1,1])]) # x2-axis171172# Constraint intersections173# 4x1 + 2x2 = 80 and 3x1 + 2x2 = 60174A_int = np.array([[4, 2], [3, 2]])175b_int = np.array([80, 60])176try:177vertex = np.linalg.solve(A_int, b_int)178if all(vertex >= 0) and all(A @ vertex <= b + 1e-6):179vertices.append(vertex)180except:181pass182183vertices = np.array(vertices)184185# Graphical solution186fig, ax = plt.subplots(figsize=(10, 8))187188# Plot constraints189x1_range = np.linspace(0, 30, 500)190191# Labor constraint: 4x1 + 2x2 <= 80192x2_labor = (80 - 4*x1_range) / 2193ax.plot(x1_range, x2_labor, 'b-', linewidth=2, label='Labor: $4x_1 + 2x_2 \\leq 80$')194ax.fill_between(x1_range, 0, np.minimum(x2_labor, 50), where=(x2_labor >= 0),195alpha=0.2, color='blue')196197# Wood constraint: 3x1 + 2x2 <= 60198x2_wood = (60 - 3*x1_range) / 2199ax.plot(x1_range, x2_wood, 'g-', linewidth=2, label='Wood: $3x_1 + 2x_2 \\leq 60$')200ax.fill_between(x1_range, 0, np.minimum(x2_wood, 50), where=(x2_wood >= 0),201alpha=0.2, color='green')202203# Feasible region204feasible_x1 = [0, 0, x_optimal[0], 20, 0]205feasible_x2 = [0, 30, x_optimal[1], 0, 0]206ax.fill(feasible_x1, feasible_x2, alpha=0.3, color='yellow',207edgecolor='black', linewidth=2, label='Feasible Region')208209# Objective function iso-profit lines210for profit in [200, 400, 600]:211x2_obj = (profit - 30*x1_range) / 20212ax.plot(x1_range, x2_obj, '--', alpha=0.5, color='red', linewidth=1)213214# Optimal iso-profit line215x2_opt = (z_optimal - 30*x1_range) / 20216ax.plot(x1_range, x2_opt, 'r-', linewidth=2.5,217label=f'Optimal: $z = {z_optimal:.1f}$')218219# Plot vertices220for i, v in enumerate(vertices):221if all(v >= 0):222obj_val = c @ v223ax.plot(v[0], v[1], 'ko', markersize=8)224ax.annotate(f'({v[0]:.1f}, {v[1]:.1f})\\n$z={obj_val:.1f}$',225xy=(v[0], v[1]), xytext=(v[0]+1.5, v[1]+2),226fontsize=9, ha='left')227228# Optimal solution229ax.plot(x_optimal[0], x_optimal[1], 'r*', markersize=20,230label=f'Optimal: $x_1={x_optimal[0]:.2f}$, $x_2={x_optimal[1]:.2f}$')231232ax.set_xlabel('$x_1$ (Tables)', fontsize=12)233ax.set_ylabel('$x_2$ (Chairs)', fontsize=12)234ax.set_title('Graphical Solution: Production Planning LP', fontsize=14)235ax.set_xlim(-2, 28)236ax.set_ylim(-2, 45)237ax.grid(True, alpha=0.3)238ax.legend(loc='upper right', fontsize=9)239ax.axhline(y=0, color='k', linewidth=0.5)240ax.axvline(x=0, color='k', linewidth=0.5)241242plt.tight_layout()243plt.savefig('linear_programming_graphical_solution.pdf', dpi=150, bbox_inches='tight')244plt.close()245\end{pycode}246247\begin{figure}[H]248\centering249\includegraphics[width=0.95\textwidth]{linear_programming_graphical_solution.pdf}250\caption{Graphical solution of the two-dimensional production planning linear program showing251the feasible region (yellow polygon) bounded by labor and wood constraints, iso-profit lines252(red dashed) representing constant objective values, and the optimal solution at the intersection253of active constraints. The optimal vertex yields maximum profit $z^* = \py{f"{z_optimal:.2f}"}$254by producing $x_1^* = \py{f"{x_optimal[0]:.2f}"}$ tables and $x_2^* = \py{f"{x_optimal[1]:.2f}"}$ chairs,255demonstrating the fundamental theorem that optimality occurs at an extreme point of the polytope.}256\end{figure}257258\subsection{Simplex Tableau Evolution}259260The simplex algorithm iteratively improves the solution by pivoting through basic feasible solutions.261We demonstrate the tableau method for a standard form LP. Converting our problem to standard form262by introducing slack variables $s_1, s_2 \geq 0$:263264\begin{equation}265\begin{aligned}266\text{maximize} \quad & z = 30x_1 + 20x_2 + 0s_1 + 0s_2 \\267\text{subject to} \quad & 4x_1 + 2x_2 + s_1 = 80 \\268& 3x_1 + 2x_2 + s_2 = 60 \\269& x_1, x_2, s_1, s_2 \geq 0270\end{aligned}271\end{equation}272273\begin{pycode}274# Simplex tableau implementation275def simplex_iteration(tableau, basis):276"""Perform one iteration of simplex method"""277m, n = tableau.shape278m -= 1 # Exclude objective row279280# Check optimality (all reduced costs <= 0 for maximization)281if np.all(tableau[-1, :-1] <= 1e-10):282return tableau, basis, True283284# Select entering variable (most positive reduced cost)285entering = np.argmax(tableau[-1, :-1])286287# Select leaving variable (minimum ratio test)288ratios = []289for i in range(m):290if tableau[i, entering] > 1e-10:291ratios.append(tableau[i, -1] / tableau[i, entering])292else:293ratios.append(np.inf)294leaving = np.argmin(ratios)295296# Update basis297basis[leaving] = entering298299# Pivot operation300pivot = tableau[leaving, entering]301tableau[leaving, :] /= pivot302303for i in range(m + 1):304if i != leaving:305tableau[i, :] -= tableau[i, entering] * tableau[leaving, :]306307return tableau, basis, False308309# Initial tableau setup310# Variables: x1, x2, s1, s2, RHS311initial_tableau = np.array([312[4.0, 2.0, 1.0, 0.0, 80.0], # Labor constraint313[3.0, 2.0, 0.0, 1.0, 60.0], # Wood constraint314[-30.0, -20.0, 0.0, 0.0, 0.0] # Objective (negated for maximization)315], dtype=float)316317basis = [2, 3] # Initial basis: s1, s2318tableaus = [initial_tableau.copy()]319iteration = 0320321tableau = initial_tableau.copy()322optimal = False323while not optimal and iteration < 10:324tableau, basis, optimal = simplex_iteration(tableau, basis)325tableaus.append(tableau.copy())326iteration += 1327328# Visualization of tableau evolution329fig, axes = plt.subplots(2, 2, figsize=(14, 10))330axes = axes.flatten()331332var_names = ['$x_1$', '$x_2$', '$s_1$', '$s_2$', 'RHS']333334for idx, (ax, tab) in enumerate(zip(axes, tableaus[:4])):335# Display tableau as table336ax.axis('tight')337ax.axis('off')338339# Create table data340table_data = []341for i in range(tab.shape[0] - 1):342row = [f'{val:.2f}' for val in tab[i, :]]343table_data.append(row)344345# Objective row346obj_row = [f'{val:.2f}' for val in tab[-1, :]]347table_data.append(obj_row)348349# Row labels350row_labels = [f'Row {i+1}' for i in range(tab.shape[0] - 1)] + ['$z$']351352table = ax.table(cellText=table_data, colLabels=var_names,353rowLabels=row_labels, cellLoc='center',354loc='center', bbox=[0, 0, 1, 1])355table.auto_set_font_size(False)356table.set_fontsize(9)357table.scale(1, 2)358359# Color code360for i in range(len(row_labels)):361table[(i, -1)].set_facecolor('#E8E8E8')362for j in range(len(var_names)):363table[(0, j)].set_facecolor('#40466e')364table[(0, j)].set_text_props(weight='bold', color='white')365366if idx == 0:367ax.set_title('Initial Tableau (Iteration 0)', fontsize=12, weight='bold')368elif idx < len(tableaus) - 1:369ax.set_title(f'Iteration {idx}', fontsize=12, weight='bold')370else:371ax.set_title(f'Optimal Tableau (Iteration {idx})', fontsize=12, weight='bold',372color='darkgreen')373374plt.tight_layout()375plt.savefig('linear_programming_simplex_tableaus.pdf', dpi=150, bbox_inches='tight')376plt.close()377\end{pycode}378379\begin{figure}[H]380\centering381\includegraphics[width=0.95\textwidth]{linear_programming_simplex_tableaus.pdf}382\caption{Evolution of simplex tableaus from initial basic feasible solution to optimal solution.383Each iteration performs a pivot operation, exchanging a non-basic variable (entering) with a basic384variable (leaving) to move to an adjacent vertex with improved objective value. The bottom row shows385reduced costs; optimality is reached when all reduced costs are non-positive for a maximization problem.386The algorithm converges in $\py{len(tableaus)-1}$ iterations, demonstrating the finite convergence387property of the simplex method on this bounded linear program.}388\end{figure}389390\section{Duality and Sensitivity Analysis}391392\subsection{Dual Problem Formulation}393394The dual of our primal production planning problem provides economic interpretation through395shadow prices. The dual variables $y_1$ and $y_2$ represent the marginal value of each396resource (labor and wood).397398The dual problem is:399\begin{equation}400\begin{aligned}401\text{minimize} \quad & w = 80y_1 + 60y_2 \\402\text{subject to} \quad & 4y_1 + 3y_2 \geq 30 \quad \text{(table)} \\403& 2y_1 + 2y_2 \geq 20 \quad \text{(chair)} \\404& y_1, y_2 \geq 0405\end{aligned}406\end{equation}407408Before proceeding with sensitivity analysis, we present the dual problem formulation and409its economic interpretation. The shadow prices indicate how much the objective would improve410per unit increase in each resource constraint, assuming the current basis remains optimal.411412\begin{pycode}413# Solve dual problem414c_dual = b # Dual objective = primal RHS415A_dual = -A.T # Dual constraints = transpose of primal416b_dual = -c # Dual RHS = negative primal objective417418result_dual = linprog(c_dual, A_ub=A_dual, b_ub=b_dual,419bounds=[(0, None), (0, None)], method='highs')420y_optimal = result_dual.x421w_optimal = result_dual.fun422423# Verify strong duality424print(f"Primal optimal: z* = {z_optimal:.4f}")425print(f"Dual optimal: w* = {w_optimal:.4f}")426print(f"Duality gap: {abs(z_optimal - w_optimal):.2e}")427428# Shadow prices from primal solution (should equal dual solution)429print(f"Shadow prices from primal: {shadow_prices}")430print(f"Dual solution y*: {y_optimal}")431432# Sensitivity analysis - varying RHS parameters433b1_range = np.linspace(60, 100, 50) # Labor variation434b2_range = np.linspace(40, 80, 50) # Wood variation435436z_labor_variation = []437z_wood_variation = []438439for b1_new in b1_range:440res = linprog(-c, A_ub=A, b_ub=[b1_new, b[1]],441bounds=[(0, None), (0, None)], method='highs')442z_labor_variation.append(-res.fun if res.success else np.nan)443444for b2_new in b2_range:445res = linprog(-c, A_ub=A, b_ub=[b[0], b2_new],446bounds=[(0, None), (0, None)], method='highs')447z_wood_variation.append(-res.fun if res.success else np.nan)448449# Sensitivity visualization450fig, axes = plt.subplots(2, 2, figsize=(14, 10))451452# Plot 1: RHS sensitivity for labor453ax1 = axes[0, 0]454ax1.plot(b1_range, z_labor_variation, 'b-', linewidth=2.5,455label='Optimal profit')456# Linear approximation using shadow price457z_linear_labor = z_optimal + shadow_prices[0] * (b1_range - b[0])458ax1.plot(b1_range, z_linear_labor, 'r--', linewidth=2,459label=f'Linear approx. (shadow price = {shadow_prices[0]:.2f})')460ax1.axvline(x=b[0], color='gray', linestyle=':', alpha=0.7,461label=f'Current value = {b[0]}')462ax1.set_xlabel('Labor hours available', fontsize=11)463ax1.set_ylabel('Optimal profit (\$)', fontsize=11)464ax1.set_title('Sensitivity to Labor Constraint', fontsize=12, weight='bold')465ax1.legend(fontsize=9)466ax1.grid(True, alpha=0.3)467468# Plot 2: RHS sensitivity for wood469ax2 = axes[0, 1]470ax2.plot(b2_range, z_wood_variation, 'g-', linewidth=2.5,471label='Optimal profit')472z_linear_wood = z_optimal + shadow_prices[1] * (b2_range - b[1])473ax2.plot(b2_range, z_linear_wood, 'r--', linewidth=2,474label=f'Linear approx. (shadow price = {shadow_prices[1]:.2f})')475ax2.axvline(x=b[1], color='gray', linestyle=':', alpha=0.7,476label=f'Current value = {b[1]}')477ax2.set_xlabel('Wood board-feet available', fontsize=11)478ax2.set_ylabel('Optimal profit (\$)', fontsize=11)479ax2.set_title('Sensitivity to Wood Constraint', fontsize=12, weight='bold')480ax2.legend(fontsize=9)481ax2.grid(True, alpha=0.3)482483# Plot 3: Objective coefficient sensitivity for x1484ax3 = axes[1, 0]485c1_range = np.linspace(15, 45, 50)486z_c1_variation = []487x1_c1_variation = []488489for c1_new in c1_range:490res = linprog(-np.array([c1_new, c[1]]), A_ub=A, b_ub=b,491bounds=[(0, None), (0, None)], method='highs')492z_c1_variation.append(-res.fun if res.success else np.nan)493x1_c1_variation.append(res.x[0] if res.success else np.nan)494495ax3_twin = ax3.twinx()496line1 = ax3.plot(c1_range, z_c1_variation, 'b-', linewidth=2.5,497label='Optimal profit')498ax3.axvline(x=c[0], color='gray', linestyle=':', alpha=0.7)499ax3.set_xlabel('Profit per table $c_1$ (\$)', fontsize=11)500ax3.set_ylabel('Optimal profit (\$)', fontsize=11, color='blue')501ax3.tick_params(axis='y', labelcolor='blue')502503line2 = ax3_twin.plot(c1_range, x1_c1_variation, 'r--', linewidth=2,504label='Tables produced')505ax3_twin.set_ylabel('Optimal $x_1$ (tables)', fontsize=11, color='red')506ax3_twin.tick_params(axis='y', labelcolor='red')507ax3.set_title('Sensitivity to Table Profit Coefficient', fontsize=12, weight='bold')508ax3.grid(True, alpha=0.3)509510# Combined legend511lines1, labels1 = ax3.get_legend_handles_labels()512lines2, labels2 = ax3_twin.get_legend_handles_labels()513ax3.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=9)514515# Plot 4: 2D sensitivity region516ax4 = axes[1, 1]517c1_2d = np.linspace(20, 40, 30)518c2_2d = np.linspace(10, 30, 30)519C1_mesh, C2_mesh = np.meshgrid(c1_2d, c2_2d)520Z_mesh = np.zeros_like(C1_mesh)521522for i in range(len(c1_2d)):523for j in range(len(c2_2d)):524res = linprog(-np.array([c1_2d[i], c2_2d[j]]), A_ub=A, b_ub=b,525bounds=[(0, None), (0, None)], method='highs')526Z_mesh[j, i] = -res.fun if res.success else np.nan527528cs = ax4.contourf(C1_mesh, C2_mesh, Z_mesh, levels=15, cmap='viridis')529ax4.plot(c[0], c[1], 'r*', markersize=20, label='Current coefficients')530cbar = plt.colorbar(cs, ax=ax4)531cbar.set_label('Optimal profit (\$)', fontsize=10)532ax4.set_xlabel('Table profit $c_1$ (\$)', fontsize=11)533ax4.set_ylabel('Chair profit $c_2$ (\$)', fontsize=11)534ax4.set_title('Objective Coefficient Sensitivity Map', fontsize=12, weight='bold')535ax4.legend(fontsize=9)536ax4.grid(True, alpha=0.3, color='white', linewidth=0.5)537538plt.tight_layout()539plt.savefig('linear_programming_sensitivity_analysis.pdf', dpi=150, bbox_inches='tight')540plt.close()541\end{pycode}542543\begin{figure}[H]544\centering545\includegraphics[width=0.98\textwidth]{linear_programming_sensitivity_analysis.pdf}546\caption{Comprehensive sensitivity analysis examining the robustness of the optimal solution to547parameter perturbations. Top row shows the linear relationship between resource availability548(labor and wood) and optimal profit, with shadow prices representing the marginal value of each549resource within the valid range. Bottom left illustrates how objective coefficient changes affect550both profit and production decisions. Bottom right presents a two-dimensional contour map showing551optimal profit as a function of both objective coefficients simultaneously. Shadow prices are552$y_1^* = \py{f"{shadow_prices[0]:.3f}"}$ per labor hour and $y_2^* = \py{f"{shadow_prices[1]:.3f}"}$553per board-foot of wood, indicating which resource constraint is more valuable to relax.}554\end{figure}555556\section{Applications and Extensions}557558\subsection{Transportation Problem}559560Linear programming naturally models transportation and logistics problems. We demonstrate561a small-scale transportation problem with 3 suppliers and 3 customers, minimizing total562shipping costs while satisfying supply and demand constraints.563564\begin{pycode}565# Transportation problem setup566# Supply from 3 warehouses: [100, 150, 120]567# Demand at 3 stores: [80, 130, 160]568# Cost matrix ($/unit shipped from warehouse i to store j)569570supply = np.array([100, 150, 120])571demand = np.array([80, 130, 160])572cost_matrix = np.array([573[8, 6, 10], # Warehouse 1 to stores 1,2,3574[9, 12, 13], # Warehouse 2 to stores 1,2,3575[14, 9, 16] # Warehouse 3 to stores 1,2,3576])577578# Formulate as LP: minimize sum(c_ij * x_ij)579# Variables: x_ij = units shipped from warehouse i to store j580n_warehouses = len(supply)581n_stores = len(demand)582n_vars = n_warehouses * n_stores583584# Objective coefficients (flatten cost matrix)585c_transport = cost_matrix.flatten()586587# Supply constraints: sum_j x_ij <= supply_i588A_supply = np.zeros((n_warehouses, n_vars))589for i in range(n_warehouses):590for j in range(n_stores):591A_supply[i, i * n_stores + j] = 1592b_supply = supply593594# Demand constraints: sum_i x_ij >= demand_j (convert to <=)595A_demand = np.zeros((n_stores, n_vars))596for j in range(n_stores):597for i in range(n_warehouses):598A_demand[j, i * n_stores + j] = -1599b_demand = -demand600601# Combine constraints602A_transport = np.vstack([A_supply, A_demand])603b_transport = np.hstack([b_supply, b_demand])604605# Solve606result_transport = linprog(c_transport, A_ub=A_transport, b_ub=b_transport,607bounds=[(0, None)] * n_vars, method='highs')608x_transport = result_transport.x.reshape(n_warehouses, n_stores)609total_cost = result_transport.fun610611# Visualization of transportation solution612fig, axes = plt.subplots(1, 2, figsize=(14, 6))613614# Plot 1: Transportation flow diagram615ax1 = axes[0]616ax1.axis('off')617ax1.set_xlim(0, 10)618ax1.set_ylim(0, 10)619620# Draw warehouses (left side)621warehouse_y = np.linspace(2, 8, n_warehouses)622for i, y in enumerate(warehouse_y):623circle = plt.Circle((2, y), 0.4, color='steelblue', alpha=0.7)624ax1.add_patch(circle)625ax1.text(2, y, f'W{i+1}\\n{supply[i]}', ha='center', va='center',626fontsize=9, weight='bold', color='white')627628# Draw stores (right side)629store_y = np.linspace(2, 8, n_stores)630for j, y in enumerate(store_y):631circle = plt.Circle((8, y), 0.4, color='coral', alpha=0.7)632ax1.add_patch(circle)633ax1.text(8, y, f'S{j+1}\\n{demand[j]}', ha='center', va='center',634fontsize=9, weight='bold', color='white')635636# Draw flows637max_flow = x_transport.max()638for i in range(n_warehouses):639for j in range(n_stores):640if x_transport[i, j] > 0.1:641width = 0.5 + 3 * (x_transport[i, j] / max_flow)642ax1.arrow(2.5, warehouse_y[i], 4.9, store_y[j] - warehouse_y[i],643head_width=0.2, head_length=0.2, fc='green', ec='green',644linewidth=width, alpha=0.6)645mid_x = 5646mid_y = (warehouse_y[i] + store_y[j]) / 2647ax1.text(mid_x, mid_y, f'{x_transport[i,j]:.1f}',648fontsize=8, ha='center',649bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))650651ax1.set_title('Optimal Transportation Flow', fontsize=12, weight='bold')652ax1.text(2, 9, 'Warehouses\\n(Supply)', ha='center', fontsize=10, weight='bold')653ax1.text(8, 9, 'Stores\\n(Demand)', ha='center', fontsize=10, weight='bold')654655# Plot 2: Cost matrix heatmap with solution overlay656ax2 = axes[1]657im = ax2.imshow(x_transport, cmap='YlGn', aspect='auto')658ax2.set_xticks(np.arange(n_stores))659ax2.set_yticks(np.arange(n_warehouses))660ax2.set_xticklabels([f'Store {j+1}' for j in range(n_stores)])661ax2.set_yticklabels([f'Warehouse {i+1}' for i in range(n_warehouses)])662ax2.set_xlabel('Destination', fontsize=11)663ax2.set_ylabel('Origin', fontsize=11)664ax2.set_title('Shipment Quantities with Unit Costs', fontsize=12, weight='bold')665666# Add text annotations667for i in range(n_warehouses):668for j in range(n_stores):669text = ax2.text(j, i, f'{x_transport[i,j]:.1f}\\n(\${cost_matrix[i,j]})',670ha='center', va='center', color='black', fontsize=9)671672cbar = plt.colorbar(im, ax=ax2)673cbar.set_label('Units shipped', fontsize=10)674675plt.tight_layout()676plt.savefig('linear_programming_transportation.pdf', dpi=150, bbox_inches='tight')677plt.close()678\end{pycode}679680\begin{figure}[H]681\centering682\includegraphics[width=0.98\textwidth]{linear_programming_transportation.pdf}683\caption{Optimal solution to the transportation problem showing flow diagram (left) with arrow684widths proportional to shipment quantities and heatmap (right) displaying optimal allocation685with unit costs. The solution minimizes total transportation cost of \$\py{f"{total_cost:.2f}"}686while satisfying all supply constraints at warehouses and demand requirements at stores. This687classical application demonstrates LP's power in logistics optimization, where the dual variables688provide shadow prices indicating marginal costs of increasing supply or demand at each location.}689\end{figure}690691\section{Results Summary}692693\subsection{Optimal Solutions}694695\begin{pycode}696print(r"\begin{table}[H]")697print(r"\centering")698print(r"\caption{Optimal Solutions for LP Problems}")699print(r"\begin{tabular}{lccc}")700print(r"\toprule")701print(r"Problem & Variables & Objective Value & Method \\")702print(r"\midrule")703print(f"Production Planning & $x_1^* = {x_optimal[0]:.2f}$, $x_2^* = {x_optimal[1]:.2f}$ & "704f"\${z_optimal:.2f} & Simplex \\\\")705print(f"Dual Problem & $y_1^* = {y_optimal[0]:.2f}$, $y_2^* = {y_optimal[1]:.2f}$ & "706f"\${w_optimal:.2f} & Dual Simplex \\\\")707print(f"Transportation & Total shipment = {np.sum(x_transport):.1f} units & "708f"\${total_cost:.2f} & Interior Point \\\\")709print(r"\bottomrule")710print(r"\end{tabular}")711print(r"\label{tab:solutions}")712print(r"\end{table}")713\end{pycode}714715\subsection{Shadow Prices and Economic Interpretation}716717\begin{pycode}718print(r"\begin{table}[H]")719print(r"\centering")720print(r"\caption{Sensitivity Analysis: Shadow Prices and Reduced Costs}")721print(r"\begin{tabular}{lccl}")722print(r"\toprule")723print(r"Resource/Variable & Shadow Price & Reduced Cost & Interpretation \\")724print(r"\midrule")725print(f"Labor (hours) & \${shadow_prices[0]:.3f} & --- & "726r"Value of 1 additional labor hour \\")727print(f"Wood (board-ft) & \${shadow_prices[1]:.3f} & --- & "728r"Value of 1 additional board-foot \\")729print(f"$x_1$ (tables) & --- & \${reduced_costs[0]:.3f} & "730r"At optimal basis \\")731print(f"$x_2$ (chairs) & --- & \${reduced_costs[1]:.3f} & "732r"At optimal basis \\")733print(r"\bottomrule")734print(r"\end{tabular}")735print(r"\label{tab:sensitivity}")736print(r"\end{table}")737\end{pycode}738739\section{Discussion}740741\begin{example}[Economic Interpretation of Shadow Prices]742The shadow price for labor ($y_1^* = \py{f"{shadow_prices[0]:.3f}"}$) indicates that each743additional hour of labor would increase profit by approximately \$\py{f"{shadow_prices[0]:.2f}"},744assuming the current basis remains optimal. Similarly, the shadow price for wood745($y_2^* = \py{f"{shadow_prices[1]:.3f}"}$) values each additional board-foot at746\$\py{f"{shadow_prices[1]:.2f}"}. These values guide resource acquisition decisions: if labor747costs less than \$\py{f"{shadow_prices[0]:.2f}"}/hour or wood costs less than748\$\py{f"{shadow_prices[1]:.2f}"}/board-foot, purchasing additional resources increases profitability.749\end{example}750751\begin{remark}[Complementary Slackness Verification]752At the optimal solution, active constraints (labor and wood) have positive shadow prices,753while slack variables have zero reduced costs. Non-binding constraints would have zero shadow754prices, and variables at their bounds have non-zero reduced costs indicating the objective755deterioration per unit forced into the basis.756\end{remark}757758\subsection{Computational Complexity}759760The simplex algorithm's worst-case complexity is exponential in the number of variables,761but it typically performs very well in practice with polynomial average-case behavior.762Interior point methods guarantee polynomial-time complexity $O(n^{3.5})$ for problems763with $n$ variables, making them preferable for very large-scale applications.764765\section{Conclusions}766767This comprehensive analysis demonstrates linear programming methodology and implementation:768769\begin{enumerate}770\item The production planning problem achieves optimal profit $z^* = \py{f"{z_optimal:.2f}"}$771by producing $x_1^* = \py{f"{x_optimal[0]:.2f}"}$ tables and $x_2^* = \py{f"{x_optimal[1]:.2f}"}$ chairs.772773\item The simplex algorithm converges in $\py{len(tableaus)-1}$ iterations by systematically774exploring vertices of the feasible polytope until reaching optimality.775776\item Strong duality is verified: primal and dual optimal values are equal777($z^* = w^* = \py{f"{z_optimal:.2f}"}$), confirming the fundamental duality theorem.778779\item Shadow prices ($y_1^* = \py{f"{shadow_prices[0]:.3f}"}$,780$y_2^* = \py{f"{shadow_prices[1]:.3f}"}$) quantify the marginal value of relaxing resource constraints.781782\item The transportation application demonstrates LP versatility in logistics optimization,783achieving minimum cost \$\py{f"{total_cost:.2f}"} while satisfying all supply-demand constraints.784785\item Sensitivity analysis reveals the range over which the current basis remains optimal,786guiding robust decision-making under parameter uncertainty.787\end{enumerate}788789Linear programming provides a powerful framework for optimization problems with linear objectives790and constraints, with applications spanning production planning, transportation, finance, scheduling,791and resource allocation.792793\section*{Further Reading}794795\begin{itemize}796\item Bertsimas, D., \& Tsitsiklis, J. N. \textit{Introduction to Linear Optimization}. Athena Scientific, 1997.797\item Dantzig, G. B. \textit{Linear Programming and Extensions}. Princeton University Press, 1963.798\item Vanderbei, R. J. \textit{Linear Programming: Foundations and Extensions}, 5th ed. Springer, 2020.799\item Chvátal, V. \textit{Linear Programming}. W. H. Freeman, 1983.800\item Bazaraa, M. S., Jarvis, J. J., \& Sherali, H. D. \textit{Linear Programming and Network Flows}, 4th ed. Wiley, 2010.801\item Luenberger, D. G., \& Ye, Y. \textit{Linear and Nonlinear Programming}, 4th ed. Springer, 2016.802\item Nocedal, J., \& Wright, S. J. \textit{Numerical Optimization}, 2nd ed. Springer, 2006.803\item Papadimitriou, C. H., \& Steiglitz, K. \textit{Combinatorial Optimization: Algorithms and Complexity}. Dover, 1998.804\item Schrijver, A. \textit{Theory of Linear and Integer Programming}. Wiley, 1998.805\item Winston, W. L., \& Goldberg, J. B. \textit{Operations Research: Applications and Algorithms}, 4th ed. Thomson, 2004.806\item Hillier, F. S., \& Lieberman, G. J. \textit{Introduction to Operations Research}, 10th ed. McGraw-Hill, 2015.807\item Taha, H. A. \textit{Operations Research: An Introduction}, 10th ed. Pearson, 2017.808\item Wolsey, L. A. \textit{Integer Programming}, 2nd ed. Wiley, 2020.809\item Karmarkar, N. ``A new polynomial-time algorithm for linear programming.'' \textit{Combinatorica} 4.4 (1984): 373-395.810\item Klee, V., \& Minty, G. J. ``How good is the simplex algorithm?'' \textit{Inequalities} 3 (1972): 159-175.811\end{itemize}812813\end{document}814815816