""" Unit test for Linear Programming via Simplex Algorithm. """ from __future__ import division, print_function, absolute_import import numpy as np from numpy.testing import (assert_, assert_array_almost_equal, assert_allclose, assert_almost_equal, assert_raises, assert_equal, run_module_suite) from scipy.optimize import linprog, OptimizeWarning from scipy._lib._numpy_compat import _assert_warns def lpgen_2d(m,n): """ -> A b c LP test: m*n vars, m+n constraints row sums == n/m, col sums == 1 https://gist.github.com/denis-bz/8647461 """ np.random.seed(0) c = - np.random.exponential(size=(m,n)) Arow = np.zeros((m,m*n)) brow = np.zeros(m) for j in range(m): j1 = j + 1 Arow[j,j*n:j1*n] = 1 brow[j] = n/m Acol = np.zeros((n,m*n)) bcol = np.zeros(n) for j in range(n): j1 = j + 1 Acol[j,j::n] = 1 bcol[j] = 1 A = np.vstack((Arow,Acol)) b = np.hstack((brow,bcol)) return A, b, c.ravel() def _assert_infeasible(res): # res: linprog result object assert_(not res.success, "incorrectly reported success") assert_equal(res.status, 2, "failed to report infeasible status") def _assert_unbounded(res): # res: linprog result object assert_(not res.success, "incorrectly reported success") assert_equal(res.status, 3, "failed to report unbounded status") def _assert_success(res, desired_fun=None, desired_x=None): # res: linprog result object # desired_fun: desired objective function value or None # desired_x: desired solution or None assert_(res.success) assert_equal(res.status, 0) if desired_fun is not None: assert_allclose(res.fun, desired_fun, err_msg="converged to an unexpected objective value") if desired_x is not None: assert_allclose(res.x, desired_x, err_msg="converged to an unexpected solution") def test_aliasing_b_ub(): c = np.array([1.0]) A_ub = np.array([[1.0]]) b_ub_orig = np.array([3.0]) b_ub = b_ub_orig.copy() bounds = (-4.0, np.inf) res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds) _assert_success(res, desired_fun=-4, desired_x=[-4]) assert_allclose(b_ub_orig, b_ub) def test_aliasing_b_eq(): c = np.array([1.0]) A_eq = np.array([[1.0]]) b_eq_orig = np.array([3.0]) b_eq = b_eq_orig.copy() bounds = (-4.0, np.inf) res = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=bounds) _assert_success(res, desired_fun=3, desired_x=[3]) assert_allclose(b_eq_orig, b_eq) def test_bounds_second_form_unbounded_below(): c = np.array([1.0]) A_eq = np.array([[1.0]]) b_eq = np.array([3.0]) bounds = (None, 10.0) res = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=bounds) _assert_success(res, desired_fun=3, desired_x=[3]) def test_bounds_second_form_unbounded_above(): c = np.array([1.0]) A_eq = np.array([[1.0]]) b_eq = np.array([3.0]) bounds = (1.0, None) res = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=bounds) _assert_success(res, desired_fun=3, desired_x=[3]) def test_non_ndarray_args(): c = [1.0] A_ub = [[1.0]] b_ub = [3.0] A_eq = [[1.0]] b_eq = [2.0] bounds = (-1.0, 10.0) res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds) _assert_success(res, desired_fun=2, desired_x=[2]) def test_linprog_upper_bound_constraints(): # Maximize a linear function subject to only linear upper bound constraints. # http://www.dam.brown.edu/people/huiwang/classes/am121/Archive/simplex_121_c.pdf c = np.array([3,2])*-1 # maximize A_ub = [[2,1], [1,1], [1,0]] b_ub = [10,8,4] res = (linprog(c,A_ub=A_ub,b_ub=b_ub)) _assert_success(res, desired_fun=-18, desired_x=[2, 6]) def test_linprog_mixed_constraints(): # Minimize linear function subject to non-negative variables. # http://www.statslab.cam.ac.uk/~ff271/teaching/opt/notes/notes8.pdf c = [6,3] A_ub = [[0, 3], [-1,-1], [-2, 1]] b_ub = [2,-1,-1] res = linprog(c,A_ub=A_ub,b_ub=b_ub) _assert_success(res, desired_fun=5, desired_x=[2/3, 1/3]) def test_linprog_cyclic_recovery(): # Test linprogs recovery from cycling using the Klee-Minty problem # Klee-Minty http://www.math.ubc.ca/~israel/m340/kleemin3.pdf c = np.array([100,10,1])*-1 # maximize A_ub = [[1, 0, 0], [20, 1, 0], [200,20, 1]] b_ub = [1,100,10000] res = linprog(c,A_ub=A_ub,b_ub=b_ub) _assert_success(res, desired_x=[0, 0, 10000]) def test_linprog_cyclic_bland(): # Test the effect of Bland's rule on a cycling problem c = np.array([-10, 57, 9, 24.]) A_ub = np.array([[0.5, -5.5, -2.5, 9], [0.5, -1.5, -0.5, 1], [1, 0, 0, 0]]) b_ub = [0, 0, 1] res = linprog(c, A_ub=A_ub, b_ub=b_ub, options=dict(maxiter=100)) assert_(not res.success) res = linprog(c, A_ub=A_ub, b_ub=b_ub, options=dict(maxiter=100, bland=True,)) _assert_success(res, desired_x=[1, 0, 1, 0]) def test_linprog_unbounded(): # Test linprog response to an unbounded problem c = np.array([1,1])*-1 # maximize A_ub = [[-1,1], [-1,-1]] b_ub = [-1,-2] res = linprog(c,A_ub=A_ub,b_ub=b_ub) _assert_unbounded(res) def test_linprog_infeasible(): # Test linrpog response to an infeasible problem c = [-1,-1] A_ub = [[1,0], [0,1], [-1,-1]] b_ub = [2,2,-5] res = linprog(c,A_ub=A_ub,b_ub=b_ub) _assert_infeasible(res) def test_nontrivial_problem(): # Test linprog for a problem involving all constraint types, # negative resource limits, and rounding issues. c = [-1,8,4,-6] A_ub = [[-7,-7,6,9], [1,-1,-3,0], [10,-10,-7,7], [6,-1,3,4]] b_ub = [-3,6,-6,6] A_eq = [[-10,1,1,-8]] b_eq = [-4] res = linprog(c,A_ub=A_ub,b_ub=b_ub,A_eq=A_eq,b_eq=b_eq) _assert_success(res, desired_fun=7083/1391, desired_x=[101/1391,1462/1391,0,752/1391]) def test_negative_variable(): # Test linprog with a problem with one unbounded variable and # another with a negative lower bound. c = np.array([-1,4])*-1 # maximize A_ub = np.array([[-3,1], [1, 2]], dtype=np.float64) A_ub_orig = A_ub.copy() b_ub = [6,4] x0_bounds = (-np.inf,np.inf) x1_bounds = (-3,np.inf) res = linprog(c,A_ub=A_ub,b_ub=b_ub,bounds=(x0_bounds,x1_bounds)) assert_equal(A_ub, A_ub_orig) # user input not overwritten _assert_success(res, desired_fun=-80/7, desired_x=[-8/7, 18/7]) def test_large_problem(): # Test linprog simplex with a rather large problem (400 variables, # 40 constraints) generated by https://gist.github.com/denis-bz/8647461 A,b,c = lpgen_2d(20,20) res = linprog(c,A_ub=A,b_ub=b) _assert_success(res, desired_fun=-64.049494229) def test_network_flow(): # A network flow problem with supply and demand at nodes # and with costs along directed edges. # https://www.princeton.edu/~rvdb/542/lectures/lec10.pdf c = [2, 4, 9, 11, 4, 3, 8, 7, 0, 15, 16, 18] n, p = -1, 1 A_eq = [ [n, n, p, 0, p, 0, 0, 0, 0, p, 0, 0], [p, 0, 0, p, 0, p, 0, 0, 0, 0, 0, 0], [0, 0, n, n, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, p, p, 0, 0, p, 0], [0, 0, 0, 0, n, n, n, 0, p, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, n, n, 0, 0, p], [0, 0, 0, 0, 0, 0, 0, 0, 0, n, n, n]] b_eq = [0, 19, -16, 33, 0, 0, -36] res = linprog(c=c, A_eq=A_eq, b_eq=b_eq) _assert_success(res, desired_fun=755) def test_network_flow_limited_capacity(): # A network flow problem with supply and demand at nodes # and with costs and capacities along directed edges. # http://blog.sommer-forst.de/2013/04/10/ cost = [2, 2, 1, 3, 1] bounds = [ [0, 4], [0, 2], [0, 2], [0, 3], [0, 5]] n, p = -1, 1 A_eq = [ [n, n, 0, 0, 0], [p, 0, n, n, 0], [0, p, p, 0, n], [0, 0, 0, p, p]] b_eq = [-4, 0, 0, 4] # Including the callback here ensures the solution can be # calculated correctly, even when phase 1 terminated # with some of the artificial variables as pivots # (i.e. basis[:m] contains elements corresponding to # the artificial variables) res = linprog(c=cost, A_eq=A_eq, b_eq=b_eq, bounds=bounds, callback=lambda x, **kwargs: None) _assert_success(res, desired_fun=14) def test_simplex_algorithm_wikipedia_example(): # http://en.wikipedia.org/wiki/Simplex_algorithm#Example Z = [-2, -3, -4] A_ub = [ [3, 2, 1], [2, 5, 3]] b_ub = [10, 15] res = linprog(c=Z, A_ub=A_ub, b_ub=b_ub) _assert_success(res, desired_fun=-20) def test_enzo_example(): # http://projects.scipy.org/scipy/attachment/ticket/1252/lp2.py # # Translated from Octave code at: # http://www.ecs.shimane-u.ac.jp/~kyoshida/lpeng.htm # and placed under MIT licence by Enzo Michelangeli # with permission explicitly granted by the original author, # Prof. Kazunobu Yoshida c = [4, 8, 3, 0, 0, 0] A_eq = [ [2, 5, 3, -1, 0, 0], [3, 2.5, 8, 0, -1, 0], [8, 10, 4, 0, 0, -1]] b_eq = [185, 155, 600] res = linprog(c=c, A_eq=A_eq, b_eq=b_eq) _assert_success(res, desired_fun=317.5, desired_x=[66.25, 0, 17.5, 0, 183.75, 0]) def test_enzo_example_b(): # rescued from https://github.com/scipy/scipy/pull/218 c = [2.8, 6.3, 10.8, -2.8, -6.3, -10.8] A_eq = [[-1, -1, -1, 0, 0, 0], [0, 0, 0, 1, 1, 1], [1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 1, 0], [0, 0, 1, 0, 0, 1]] b_eq = [-0.5, 0.4, 0.3, 0.3, 0.3] # Including the callback here ensures the solution can be # calculated correctly. res = linprog(c=c, A_eq=A_eq, b_eq=b_eq, callback=lambda x, **kwargs: None) _assert_success(res, desired_fun=-1.77, desired_x=[0.3, 0.2, 0.0, 0.0, 0.1, 0.3]) def test_enzo_example_c_with_degeneracy(): # rescued from https://github.com/scipy/scipy/pull/218 m = 20 c = -np.ones(m) tmp = 2*np.pi*np.arange(1, m+1)/(m+1) A_eq = np.vstack((np.cos(tmp)-1, np.sin(tmp))) b_eq = [0, 0] res = linprog(c=c, A_eq=A_eq, b_eq=b_eq) _assert_success(res, desired_fun=0, desired_x=np.zeros(m)) def test_enzo_example_c_with_unboundedness(): # rescued from https://github.com/scipy/scipy/pull/218 m = 50 c = -np.ones(m) tmp = 2*np.pi*np.arange(m)/(m+1) A_eq = np.vstack((np.cos(tmp)-1, np.sin(tmp))) b_eq = [0, 0] res = linprog(c=c, A_eq=A_eq, b_eq=b_eq) _assert_unbounded(res) def test_enzo_example_c_with_infeasibility(): # rescued from https://github.com/scipy/scipy/pull/218 m = 50 c = -np.ones(m) tmp = 2*np.pi*np.arange(m)/(m+1) A_eq = np.vstack((np.cos(tmp)-1, np.sin(tmp))) b_eq = [1, 1] res = linprog(c=c, A_eq=A_eq, b_eq=b_eq) _assert_infeasible(res) def test_callback(): # Check that callback is as advertised callback_complete = [False] last_xk = [] def cb(xk, **kwargs): kwargs.pop('tableau') assert_(isinstance(kwargs.pop('phase'), int)) assert_(isinstance(kwargs.pop('nit'), int)) i, j = kwargs.pop('pivot') assert_(np.isscalar(i)) assert_(np.isscalar(j)) basis = kwargs.pop('basis') assert_(isinstance(basis, np.ndarray)) assert_(basis.dtype == np.int_) complete = kwargs.pop('complete') assert_(isinstance(complete, bool)) if complete: last_xk.append(xk) callback_complete[0] = True else: assert_(not callback_complete[0]) # no more kwargs assert_(not kwargs) c = np.array([-3,-2]) A_ub = [[2,1], [1,1], [1,0]] b_ub = [10,8,4] res = linprog(c,A_ub=A_ub,b_ub=b_ub, callback=cb) assert_(callback_complete[0]) assert_allclose(last_xk[0], res.x) def test_unknown_options_or_solver(): c = np.array([-3,-2]) A_ub = [[2,1], [1,1], [1,0]] b_ub = [10,8,4] _assert_warns(OptimizeWarning, linprog, c, A_ub=A_ub, b_ub=b_ub, options=dict(spam='42')) assert_raises(ValueError, linprog, c, A_ub=A_ub, b_ub=b_ub, method='ekki-ekki-ekki') def test_no_constraints(): res = linprog([-1, -2]) assert_equal(res.x, [0, 0]) _assert_unbounded(res) def test_simple_bounds(): res = linprog([1, 2], bounds=(1, 2)) _assert_success(res, desired_x=[1, 1]) res = linprog([1, 2], bounds=[(1, 2), (1, 2)]) _assert_success(res, desired_x=[1, 1]) def test_invalid_inputs(): for bad_bound in [[(5, 0), (1, 2), (3, 4)], [(1, 2), (3, 4)], [(1, 2), (3, 4), (3, 4, 5)], [(1, 2), (np.inf, np.inf), (3, 4)], [(1, 2), (-np.inf, -np.inf), (3, 4)], ]: assert_raises(ValueError, linprog, [1, 2, 3], bounds=bad_bound) assert_raises(ValueError, linprog, [1,2], A_ub=[[1,2]], b_ub=[1,2]) assert_raises(ValueError, linprog, [1,2], A_ub=[[1]], b_ub=[1]) assert_raises(ValueError, linprog, [1,2], A_eq=[[1,2]], b_eq=[1,2]) assert_raises(ValueError, linprog, [1,2], A_eq=[[1]], b_eq=[1]) assert_raises(ValueError, linprog, [1,2], A_eq=[1], b_eq=1) assert_raises(ValueError, linprog, [1,2], A_ub=np.zeros((1,1,3)), b_eq=1) def test_basic_artificial_vars(): # Test if linprog succeeds when at the end of Phase 1 some artificial # variables remain basic, and the row in T corresponding to the # artificial variables is not all zero. c = np.array([-0.1, -0.07, 0.004, 0.004, 0.004, 0.004]) A_ub = np.array([[1.0, 0, 0, 0, 0, 0], [-1.0, 0, 0, 0, 0, 0], [0, -1.0, 0, 0, 0, 0], [0, 1.0, 0, 0, 0, 0], [1.0, 1.0, 0, 0, 0, 0]]) b_ub = np.array([3.0, 3.0, 3.0, 3.0, 20.0]) A_eq = np.array([[1.0, 0, -1, 1, -1, 1], [0, -1.0, -1, 1, -1, 1]]) b_eq = np.array([0, 0]) res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, callback=lambda x, **kwargs: None) _assert_success(res, desired_fun=0, desired_x=np.zeros_like(c)) if __name__ == '__main__': run_module_suite()