1.4 Linear programming in software

SciPy’s linprog minimises \(c^{\mathsf T}x\) subject to \(A_{\text {ub}}x \leq b_{\text {ub}}\), \(A_{\text {eq}}x = b_{\text {eq}}\) and bounds on each variable, using the HiGHS solvers. A maximisation is entered as the minimisation of \(-c^{\mathsf T}x\), and a \(\geq \) constraint by multiplying it by \(-1\).

Software 1.4.1 (Product mix and inspectors). Examples 1.2.1 and 1.2.2:

>>> import numpy as np
>>> from scipy.optimize import linprog
>>> c = [-20, -30]  # maximise 20 x1 + 30 x2: minimise -Z
>>> A = [[10, 40], [3, 2], [2, 3]]         # budget, machine, assembly
>>> b = [4000, 2000, 1400]
>>> r = linprog(c, A_ub=A, b_ub=b, method='highs')
>>> r.x, -r.fun, b - np.array(A) @ r.x     # plan, profit, unused resources
(array([400.,   0.]), 8000.0, array([  0., 800., 600.]))
>>> r = linprog([68.8, 68.0],  # inspectors: daily cost per inspector
...             A_ub=[[-320, -240]], b_ub=[-2000],   # at least 2000 pieces
...             bounds=[(0, 9), (0, 11)], method='highs')
>>> r.x, r.fun
(array([6.25, 0.  ]), 430.0)

Reading the output. The product mix makes \(400\) of \(C_1\) for a profit of \(8000\); the slacks are \(0\) for the budget and \(800\) and \(600\) hours of machine and assembly time. The inspection plan uses \(6.25\) Grade 1 inspectors and no Grade 2, at Rs. 430 a day, a fractional answer that the model’s divisibility assumption allowed.

MATLAB/Octave: linprog(-[20;30], [10 40; 3 2; 2 3], [4000; 2000; 1400], [], [], [0;0]).

Software 1.4.2 (A four-month plan). Example 1.2.3, with the twelve variables ordered as regular production, overtime and stock, and one balance equation per month:

>>> d = np.array([100, 150, 200, 120])     # demand in four months
>>> T = len(d)  # variables: regular, overtime, stock
>>> cost = np.r_[10*np.ones(T), 14*np.ones(T), 2*np.ones(T)]
>>> Aeq = np.zeros((T, 3*T))
>>> for t in range(T):            # stock_{t-1} + R_t + O_t - stock_t = d_t
...     Aeq[t, t] = Aeq[t, T + t] = 1; Aeq[t, 2*T + t] = -1
...     if t > 0: Aeq[t, 2*T + t - 1] = 1
...
>>> bounds = [(0, 140)]*T + [(0, 40)]*T + [(0, None)]*T
>>> r = linprog(cost, A_eq=Aeq, b_eq=d, bounds=bounds, method='highs')
>>> r.x.reshape(3, T), r.fun               # rows: regular, overtime, stock
(array([[140., 140., 140., 120.],
       [  0.,   0.,  30.,   0.],
       [ 40.,  30.,   0.,   0.]]), 5960.0)

Reading the output. Regular production is \(140\) in each of the first three months and \(120\) in the last; overtime of \(30\) is used only in month \(3\); stocks of \(40\) and \(30\) are carried out of months \(1\) and \(2\) into the peak. The total cost is Rs. 5960. Checking month \(3\): \(30 + 140 + 30 - 0 = 200\).

MATLAB/Octave: the same matrices with linprog(cost, [], [], Aeq, d, lb, ub).

Software 1.4.3 (Fitting a line by linear programming). The \(L^\infty \) and \(L^1\) fits of Figure 1 as linear programmes, with the free intercept and slope declared by the bounds (None, None), and least squares for comparison:

>>> xs = np.arange(10.0)
>>> ys = np.array([1.0, 2.1, 2.9, 4.2, 4.8, 6.1, 7.2, 7.9, 9.1, 15.0])
>>> n = len(xs)                   # L-infinity fit: min t, |y - a - bx| <= t
>>> one = np.ones(n)
>>> A = np.r_[np.c_[-one, -xs, -one], np.c_[one, xs, -one]]
>>> r = linprog([0, 0, 1], A_ub=A, b_ub=np.r_[-ys, ys],
...             bounds=[(None, None)]*2 + [(0, None)], method='highs')
>>> cheb = r.x[:2]
>>> I = np.eye(n)  # L1 fit: min sum e_i, |y_i - a - b x_i| <= e_i
>>> A = np.r_[np.c_[-np.ones(n), -xs, -I], np.c_[np.ones(n), xs, -I]]
>>> r = linprog(np.r_[0, 0, np.ones(n)], A_ub=A, b_ub=np.r_[-ys, ys],
...             bounds=[(None, None)]*2 + [(0, None)]*n, method='highs')
>>> l1 = r.x[:2]
>>> ls = np.polyfit(xs, ys, 1)[::-1]       # least squares, for comparison
>>> np.round(np.array([l1, ls, cheb]), 4)  # rows: L1, L2, L-infinity; (a, b)
array([[ 1.    ,  1.02  ],
       [ 0.2945,  1.2745],
       [-1.1722,  1.5556]])

Reading the output. The \(L^1\) line is \(1 + 1.02x\), close to the trend of the first nine points; least squares gives \(0.29 + 1.27x\) and the Chebyshev fit \(-1.17 + 1.56x\). One bad point moves the slope by \(25\%\) and \(53\%\) for the last two. The \(L^1\) fit is the regression analogue of the median, and like it, robust.

MATLAB/Octave: linprog with the same stacked constraint matrices.

Questions on this section

Stuck on something here? Ask below and it stays attached to this topic.