6.5 Sensitivity in software
Software 6.5.1 (Ranging). From the optimal basis \(\{s_3, x_1, x_2\}\) of the running example: \(x_B\) and \(y\), the range of each \(b_i\) from Theorem 6.1.1, and the range of each \(c_j\) over which the reduced costs keep their sign, found on a fine grid:
>>> import numpy as np
>>> from scipy.optimize import linprog
>>> A = np.array([[2., 1], [2, 4], [-1, 1]]); b = np.array([26., 56, 5])
>>> c = np.array([10., 15])
>>> Af = np.hstack([A, np.eye(3)]); cf = np.r_[c, 0, 0, 0] # with slacks
>>> basis = [4, 0, 1] # s3, x1, x2: the optimal basis
>>> Binv = np.linalg.inv(Af[:, basis]); xB = Binv @ b
>>> y = cf[basis] @ Binv; xB, y # basic values and shadow prices
(array([ 3., 8., 10.]), array([1.66666667, 3.33333333, 0. ]))
>>> for i in range(3): # right-hand-side ranges
... col = Binv[:, i]; t = -xB / np.where(col == 0, np.nan, col)
... lo = np.nanmax(np.where(col > 0, t, np.nan), initial=-np.inf)
... hi = np.nanmin(np.where(col < 0, t, np.nan), initial=np.inf)
... print(f"b{i+1}: {b[i] + lo:6.2f} to {b[i] + hi:6.2f}, y = {y[i]:.4f}")
...
b1: 23.00 to 56.00, y = 1.6667
b2: 26.00 to 62.00, y = 3.3333
b3: 2.00 to inf, y = 0.0000
>>> def still_optimal(cc): # all reduced costs <= 0 (maximising)
... cfull = np.r_[cc, 0, 0, 0]
... return np.all(cfull - (cfull[basis] @ Binv) @ Af <= 1e-9)
...
>>> grid = np.linspace(0, 60, 60001) # cost ranges, on a fine grid
>>> for j in range(2):
... trial = lambda t: np.where(np.arange(2) == j, t, c)
... ok = [t for t in grid if still_optimal(trial(t))]
... print(f"c{j+1}: {min(ok):.2f} to {max(ok):.2f}")
...
c1: 7.50 to 30.00
c2: 5.00 to 20.00
Reading the output. The ranges are \(23 \leq b_1 \leq 56\), \(26 \leq b_2 \leq 62\), \(b_3 \geq 2\), and \(7.5 \leq c_1 \leq 30\), \(5 \leq c_2 \leq 20\). The third resource has price \(0\) and can grow without limit, but cannot fall below \(2\), where its slack of \(3\) is used up.
MATLAB/Octave: Binv = inv(Af(:, basis)) and the same ratio formulas.
Software 6.5.2 (The value function). The optimal profit for \(241\) values of \(b_2\) from \(0\) to \(120\), its breakpoints, and the distinct slopes between them:
>>> bs = np.linspace(0, 120, 241); z = [] >>> for b2 in bs: # parametric right-hand side b2 ... r = linprog(-c, A_ub=A, b_ub=[26, b2, 5], method='highs') ... z.append(-r.fun) ... >>> slopes = np.round(np.diff(z) / np.diff(bs), 4) >>> breaks = [bs[k] for k in range(1, len(slopes)) ... if slopes[k] != slopes[k - 1]] >>> breaks, sorted(set(slopes), reverse=True) ([26.0, 62.0], [5.0, 3.3333, 0.0])
Reading the output. The slope changes at \(b_2 = 26\) and \(b_2 = 62\), exactly the ends of the range found by ranging, and the slopes are \(5\), \(\frac {10}3\) and \(0\), decreasing as Theorem 6.1.3 requires. Below \(26\) the constraints \(2x_1 + 4x_2 \leq b_2\) and \(-x_1 + x_2 \leq 5\) bind and an extra unit of \(b_2\) is worth \(5\); above \(62\) the second resource is in surplus.
MATLAB/Octave: a loop of linprog calls.
Software 6.5.3 (A new product and a new constraint). Pricing a new product with column \((1, 1, 1)\) and profit \(12\), then re-solving with it; and re-solving the original problem with the added constraint \(x_1 \leq 6\):
>>> a3, c3 = np.array([1., 1, 1]), 12.0 # a new product: price it out >>> y @ a3, c3 - y @ a3 # reduced profit > 0: worth making (5.0, 7.0) >>> r = linprog(-np.r_[c, c3], A_ub=np.c_[A, a3], b_ub=b, method='highs') >>> r.x, -r.fun (array([ 7., 10., 2.]), 244.0) >>> r = linprog(-c, A_ub=np.r_[A, [[1, 0]]], b_ub=np.r_[b, 6], ... method='highs') # a new constraint x1 <= 6 >>> r.x, -r.fun (array([ 6., 11.]), 225.0)
Reading the output. The resources the new product uses are worth \(5\), so a profit of \(12\) gives a reduced profit of \(7 > 0\): it should be made. The new optimum makes \(7\), \(10\) and \(2\) units for a profit of \(244\). The constraint \(x_1 \leq 6\) cuts off the old optimum \((8, 10)\), and the new optimum is the vertex \(C(6, 11)\) with \(Z = 225\): the restriction costs \(5\).
MATLAB/Octave: linprog with the extra column or row.
Questions on this section
Stuck on something here? Ask below and it stays attached to this topic.