test_linprog.py 93 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437
  1. """
  2. Unit test for Linear Programming
  3. """
  4. import sys
  5. import platform
  6. import numpy as np
  7. from numpy.testing import (assert_, assert_allclose, assert_equal,
  8. assert_array_less, assert_warns, suppress_warnings)
  9. from pytest import raises as assert_raises
  10. from scipy.optimize import linprog, OptimizeWarning
  11. from scipy.optimize._numdiff import approx_derivative
  12. from scipy.sparse.linalg import MatrixRankWarning
  13. from scipy.linalg import LinAlgWarning
  14. import scipy.sparse
  15. import pytest
  16. has_umfpack = True
  17. try:
  18. from scikits.umfpack import UmfpackWarning
  19. except ImportError:
  20. has_umfpack = False
  21. has_cholmod = True
  22. try:
  23. import sksparse
  24. from sksparse.cholmod import cholesky as cholmod
  25. except ImportError:
  26. has_cholmod = False
  27. def _assert_iteration_limit_reached(res, maxiter):
  28. assert_(not res.success, "Incorrectly reported success")
  29. assert_(res.success < maxiter, "Incorrectly reported number of iterations")
  30. assert_equal(res.status, 1, "Failed to report iteration limit reached")
  31. def _assert_infeasible(res):
  32. # res: linprog result object
  33. assert_(not res.success, "incorrectly reported success")
  34. assert_equal(res.status, 2, "failed to report infeasible status")
  35. def _assert_unbounded(res):
  36. # res: linprog result object
  37. assert_(not res.success, "incorrectly reported success")
  38. assert_equal(res.status, 3, "failed to report unbounded status")
  39. def _assert_unable_to_find_basic_feasible_sol(res):
  40. # res: linprog result object
  41. # The status may be either 2 or 4 depending on why the feasible solution
  42. # could not be found. If the undelying problem is expected to not have a
  43. # feasible solution, _assert_infeasible should be used.
  44. assert_(not res.success, "incorrectly reported success")
  45. assert_(res.status in (2, 4), "failed to report optimization failure")
  46. def _assert_success(res, desired_fun=None, desired_x=None,
  47. rtol=1e-8, atol=1e-8):
  48. # res: linprog result object
  49. # desired_fun: desired objective function value or None
  50. # desired_x: desired solution or None
  51. if not res.success:
  52. msg = "linprog status {0}, message: {1}".format(res.status,
  53. res.message)
  54. raise AssertionError(msg)
  55. assert_equal(res.status, 0)
  56. if desired_fun is not None:
  57. assert_allclose(res.fun, desired_fun,
  58. err_msg="converged to an unexpected objective value",
  59. rtol=rtol, atol=atol)
  60. if desired_x is not None:
  61. assert_allclose(res.x, desired_x,
  62. err_msg="converged to an unexpected solution",
  63. rtol=rtol, atol=atol)
  64. def magic_square(n):
  65. """
  66. Generates a linear program for which integer solutions represent an
  67. n x n magic square; binary decision variables represent the presence
  68. (or absence) of an integer 1 to n^2 in each position of the square.
  69. """
  70. np.random.seed(0)
  71. M = n * (n**2 + 1) / 2
  72. numbers = np.arange(n**4) // n**2 + 1
  73. numbers = numbers.reshape(n**2, n, n)
  74. zeros = np.zeros((n**2, n, n))
  75. A_list = []
  76. b_list = []
  77. # Rule 1: use every number exactly once
  78. for i in range(n**2):
  79. A_row = zeros.copy()
  80. A_row[i, :, :] = 1
  81. A_list.append(A_row.flatten())
  82. b_list.append(1)
  83. # Rule 2: Only one number per square
  84. for i in range(n):
  85. for j in range(n):
  86. A_row = zeros.copy()
  87. A_row[:, i, j] = 1
  88. A_list.append(A_row.flatten())
  89. b_list.append(1)
  90. # Rule 3: sum of rows is M
  91. for i in range(n):
  92. A_row = zeros.copy()
  93. A_row[:, i, :] = numbers[:, i, :]
  94. A_list.append(A_row.flatten())
  95. b_list.append(M)
  96. # Rule 4: sum of columns is M
  97. for i in range(n):
  98. A_row = zeros.copy()
  99. A_row[:, :, i] = numbers[:, :, i]
  100. A_list.append(A_row.flatten())
  101. b_list.append(M)
  102. # Rule 5: sum of diagonals is M
  103. A_row = zeros.copy()
  104. A_row[:, range(n), range(n)] = numbers[:, range(n), range(n)]
  105. A_list.append(A_row.flatten())
  106. b_list.append(M)
  107. A_row = zeros.copy()
  108. A_row[:, range(n), range(-1, -n - 1, -1)] = \
  109. numbers[:, range(n), range(-1, -n - 1, -1)]
  110. A_list.append(A_row.flatten())
  111. b_list.append(M)
  112. A = np.array(np.vstack(A_list), dtype=float)
  113. b = np.array(b_list, dtype=float)
  114. c = np.random.rand(A.shape[1])
  115. return A, b, c, numbers, M
  116. def lpgen_2d(m, n):
  117. """ -> A b c LP test: m*n vars, m+n constraints
  118. row sums == n/m, col sums == 1
  119. https://gist.github.com/denis-bz/8647461
  120. """
  121. np.random.seed(0)
  122. c = - np.random.exponential(size=(m, n))
  123. Arow = np.zeros((m, m * n))
  124. brow = np.zeros(m)
  125. for j in range(m):
  126. j1 = j + 1
  127. Arow[j, j * n:j1 * n] = 1
  128. brow[j] = n / m
  129. Acol = np.zeros((n, m * n))
  130. bcol = np.zeros(n)
  131. for j in range(n):
  132. j1 = j + 1
  133. Acol[j, j::n] = 1
  134. bcol[j] = 1
  135. A = np.vstack((Arow, Acol))
  136. b = np.hstack((brow, bcol))
  137. return A, b, c.ravel()
  138. def very_random_gen(seed=0):
  139. np.random.seed(seed)
  140. m_eq, m_ub, n = 10, 20, 50
  141. c = np.random.rand(n)-0.5
  142. A_ub = np.random.rand(m_ub, n)-0.5
  143. b_ub = np.random.rand(m_ub)-0.5
  144. A_eq = np.random.rand(m_eq, n)-0.5
  145. b_eq = np.random.rand(m_eq)-0.5
  146. lb = -np.random.rand(n)
  147. ub = np.random.rand(n)
  148. lb[lb < -np.random.rand()] = -np.inf
  149. ub[ub > np.random.rand()] = np.inf
  150. bounds = np.vstack((lb, ub)).T
  151. return c, A_ub, b_ub, A_eq, b_eq, bounds
  152. def nontrivial_problem():
  153. c = [-1, 8, 4, -6]
  154. A_ub = [[-7, -7, 6, 9],
  155. [1, -1, -3, 0],
  156. [10, -10, -7, 7],
  157. [6, -1, 3, 4]]
  158. b_ub = [-3, 6, -6, 6]
  159. A_eq = [[-10, 1, 1, -8]]
  160. b_eq = [-4]
  161. x_star = [101 / 1391, 1462 / 1391, 0, 752 / 1391]
  162. f_star = 7083 / 1391
  163. return c, A_ub, b_ub, A_eq, b_eq, x_star, f_star
  164. def l1_regression_prob(seed=0, m=8, d=9, n=100):
  165. '''
  166. Training data is {(x0, y0), (x1, y2), ..., (xn-1, yn-1)}
  167. x in R^d
  168. y in R
  169. n: number of training samples
  170. d: dimension of x, i.e. x in R^d
  171. phi: feature map R^d -> R^m
  172. m: dimension of feature space
  173. '''
  174. np.random.seed(seed)
  175. phi = np.random.normal(0, 1, size=(m, d)) # random feature mapping
  176. w_true = np.random.randn(m)
  177. x = np.random.normal(0, 1, size=(d, n)) # features
  178. y = w_true @ (phi @ x) + np.random.normal(0, 1e-5, size=n) # measurements
  179. # construct the problem
  180. c = np.ones(m+n)
  181. c[:m] = 0
  182. A_ub = scipy.sparse.lil_matrix((2*n, n+m))
  183. idx = 0
  184. for ii in range(n):
  185. A_ub[idx, :m] = phi @ x[:, ii]
  186. A_ub[idx, m+ii] = -1
  187. A_ub[idx+1, :m] = -1*phi @ x[:, ii]
  188. A_ub[idx+1, m+ii] = -1
  189. idx += 2
  190. A_ub = A_ub.tocsc()
  191. b_ub = np.zeros(2*n)
  192. b_ub[0::2] = y
  193. b_ub[1::2] = -y
  194. bnds = [(None, None)]*m + [(0, None)]*n
  195. return c, A_ub, b_ub, bnds
  196. def generic_callback_test(self):
  197. # Check that callback is as advertised
  198. last_cb = {}
  199. def cb(res):
  200. message = res.pop('message')
  201. complete = res.pop('complete')
  202. assert_(res.pop('phase') in (1, 2))
  203. assert_(res.pop('status') in range(4))
  204. assert_(isinstance(res.pop('nit'), int))
  205. assert_(isinstance(complete, bool))
  206. assert_(isinstance(message, str))
  207. last_cb['x'] = res['x']
  208. last_cb['fun'] = res['fun']
  209. last_cb['slack'] = res['slack']
  210. last_cb['con'] = res['con']
  211. c = np.array([-3, -2])
  212. A_ub = [[2, 1], [1, 1], [1, 0]]
  213. b_ub = [10, 8, 4]
  214. res = linprog(c, A_ub=A_ub, b_ub=b_ub, callback=cb, method=self.method)
  215. _assert_success(res, desired_fun=-18.0, desired_x=[2, 6])
  216. assert_allclose(last_cb['fun'], res['fun'])
  217. assert_allclose(last_cb['x'], res['x'])
  218. assert_allclose(last_cb['con'], res['con'])
  219. assert_allclose(last_cb['slack'], res['slack'])
  220. def test_unknown_solvers_and_options():
  221. c = np.array([-3, -2])
  222. A_ub = [[2, 1], [1, 1], [1, 0]]
  223. b_ub = [10, 8, 4]
  224. assert_raises(ValueError, linprog,
  225. c, A_ub=A_ub, b_ub=b_ub, method='ekki-ekki-ekki')
  226. assert_raises(ValueError, linprog,
  227. c, A_ub=A_ub, b_ub=b_ub, method='highs-ekki')
  228. with pytest.warns(OptimizeWarning, match="Unknown solver options:"):
  229. linprog(c, A_ub=A_ub, b_ub=b_ub,
  230. options={"rr_method": 'ekki-ekki-ekki'})
  231. def test_choose_solver():
  232. # 'highs' chooses 'dual'
  233. c = np.array([-3, -2])
  234. A_ub = [[2, 1], [1, 1], [1, 0]]
  235. b_ub = [10, 8, 4]
  236. res = linprog(c, A_ub, b_ub, method='highs')
  237. _assert_success(res, desired_fun=-18.0, desired_x=[2, 6])
  238. def test_deprecation():
  239. with pytest.warns(DeprecationWarning):
  240. linprog(1, method='interior-point')
  241. with pytest.warns(DeprecationWarning):
  242. linprog(1, method='revised simplex')
  243. with pytest.warns(DeprecationWarning):
  244. linprog(1, method='simplex')
  245. def test_highs_status_message():
  246. res = linprog(1, method='highs')
  247. msg = "Optimization terminated successfully. (HiGHS Status 7:"
  248. assert res.status == 0
  249. assert res.message.startswith(msg)
  250. A, b, c, numbers, M = magic_square(6)
  251. bounds = [(0, 1)] * len(c)
  252. integrality = [1] * len(c)
  253. options = {"time_limit": 0.1}
  254. res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs',
  255. options=options, integrality=integrality)
  256. msg = "Time limit reached. (HiGHS Status 13:"
  257. assert res.status == 1
  258. assert res.message.startswith(msg)
  259. options = {"maxiter": 10}
  260. res = linprog(c=c, A_eq=A, b_eq=b, bounds=bounds, method='highs-ds',
  261. options=options)
  262. msg = "Iteration limit reached. (HiGHS Status 14:"
  263. assert res.status == 1
  264. assert res.message.startswith(msg)
  265. res = linprog(1, bounds=(1, -1), method='highs')
  266. msg = "The problem is infeasible. (HiGHS Status 8:"
  267. assert res.status == 2
  268. assert res.message.startswith(msg)
  269. res = linprog(-1, method='highs')
  270. msg = "The problem is unbounded. (HiGHS Status 10:"
  271. assert res.status == 3
  272. assert res.message.startswith(msg)
  273. from scipy.optimize._linprog_highs import _highs_to_scipy_status_message
  274. status, message = _highs_to_scipy_status_message(58, "Hello!")
  275. msg = "The HiGHS status code was not recognized. (HiGHS Status 58:"
  276. assert status == 4
  277. assert message.startswith(msg)
  278. status, message = _highs_to_scipy_status_message(None, None)
  279. msg = "HiGHS did not provide a status code. (HiGHS Status None: None)"
  280. assert status == 4
  281. assert message.startswith(msg)
  282. def test_bug_17380():
  283. linprog([1, 1], A_ub=[[-1, 0]], b_ub=[-2.5], integrality=[1, 1])
  284. A_ub = None
  285. b_ub = None
  286. A_eq = None
  287. b_eq = None
  288. bounds = None
  289. ################
  290. # Common Tests #
  291. ################
  292. class LinprogCommonTests:
  293. """
  294. Base class for `linprog` tests. Generally, each test will be performed
  295. once for every derived class of LinprogCommonTests, each of which will
  296. typically change self.options and/or self.method. Effectively, these tests
  297. are run for many combination of method (simplex, revised simplex, and
  298. interior point) and options (such as pivoting rule or sparse treatment).
  299. """
  300. ##################
  301. # Targeted Tests #
  302. ##################
  303. def test_callback(self):
  304. generic_callback_test(self)
  305. def test_disp(self):
  306. # test that display option does not break anything.
  307. A, b, c = lpgen_2d(20, 20)
  308. res = linprog(c, A_ub=A, b_ub=b, method=self.method,
  309. options={"disp": True})
  310. _assert_success(res, desired_fun=-64.049494229)
  311. def test_docstring_example(self):
  312. # Example from linprog docstring.
  313. c = [-1, 4]
  314. A = [[-3, 1], [1, 2]]
  315. b = [6, 4]
  316. x0_bounds = (None, None)
  317. x1_bounds = (-3, None)
  318. res = linprog(c, A_ub=A, b_ub=b, bounds=(x0_bounds, x1_bounds),
  319. options=self.options, method=self.method)
  320. _assert_success(res, desired_fun=-22)
  321. def test_type_error(self):
  322. # (presumably) checks that linprog recognizes type errors
  323. # This is tested more carefully in test__linprog_clean_inputs.py
  324. c = [1]
  325. A_eq = [[1]]
  326. b_eq = "hello"
  327. assert_raises(TypeError, linprog,
  328. c, A_eq=A_eq, b_eq=b_eq,
  329. method=self.method, options=self.options)
  330. def test_aliasing_b_ub(self):
  331. # (presumably) checks that linprog does not modify b_ub
  332. # This is tested more carefully in test__linprog_clean_inputs.py
  333. c = np.array([1.0])
  334. A_ub = np.array([[1.0]])
  335. b_ub_orig = np.array([3.0])
  336. b_ub = b_ub_orig.copy()
  337. bounds = (-4.0, np.inf)
  338. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  339. method=self.method, options=self.options)
  340. _assert_success(res, desired_fun=-4, desired_x=[-4])
  341. assert_allclose(b_ub_orig, b_ub)
  342. def test_aliasing_b_eq(self):
  343. # (presumably) checks that linprog does not modify b_eq
  344. # This is tested more carefully in test__linprog_clean_inputs.py
  345. c = np.array([1.0])
  346. A_eq = np.array([[1.0]])
  347. b_eq_orig = np.array([3.0])
  348. b_eq = b_eq_orig.copy()
  349. bounds = (-4.0, np.inf)
  350. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  351. method=self.method, options=self.options)
  352. _assert_success(res, desired_fun=3, desired_x=[3])
  353. assert_allclose(b_eq_orig, b_eq)
  354. def test_non_ndarray_args(self):
  355. # (presumably) checks that linprog accepts list in place of arrays
  356. # This is tested more carefully in test__linprog_clean_inputs.py
  357. c = [1.0]
  358. A_ub = [[1.0]]
  359. b_ub = [3.0]
  360. A_eq = [[1.0]]
  361. b_eq = [2.0]
  362. bounds = (-1.0, 10.0)
  363. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  364. method=self.method, options=self.options)
  365. _assert_success(res, desired_fun=2, desired_x=[2])
  366. def test_unknown_options(self):
  367. c = np.array([-3, -2])
  368. A_ub = [[2, 1], [1, 1], [1, 0]]
  369. b_ub = [10, 8, 4]
  370. def f(c, A_ub=None, b_ub=None, A_eq=None,
  371. b_eq=None, bounds=None, options={}):
  372. linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  373. method=self.method, options=options)
  374. o = {key: self.options[key] for key in self.options}
  375. o['spam'] = 42
  376. assert_warns(OptimizeWarning, f,
  377. c, A_ub=A_ub, b_ub=b_ub, options=o)
  378. def test_integrality_without_highs(self):
  379. # ensure that using `integrality` parameter without `method='highs'`
  380. # raises warning and produces correct solution to relaxed problem
  381. # source: https://en.wikipedia.org/wiki/Integer_programming#Example
  382. A_ub = np.array([[-1, 1], [3, 2], [2, 3]])
  383. b_ub = np.array([1, 12, 12])
  384. c = -np.array([0, 1])
  385. bounds = [(0, np.inf)] * len(c)
  386. integrality = [1] * len(c)
  387. with np.testing.assert_warns(OptimizeWarning):
  388. res = linprog(c=c, A_ub=A_ub, b_ub=b_ub, bounds=bounds,
  389. method=self.method, integrality=integrality)
  390. np.testing.assert_allclose(res.x, [1.8, 2.8])
  391. np.testing.assert_allclose(res.fun, -2.8)
  392. def test_invalid_inputs(self):
  393. def f(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None):
  394. linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  395. method=self.method, options=self.options)
  396. # Test ill-formatted bounds
  397. assert_raises(ValueError, f, [1, 2, 3], bounds=[(1, 2), (3, 4)])
  398. assert_raises(ValueError, f, [1, 2, 3], bounds=[(1, 2), (3, 4), (3, 4, 5)])
  399. assert_raises(ValueError, f, [1, 2, 3], bounds=[(1, -2), (1, 2)])
  400. # Test other invalid inputs
  401. assert_raises(ValueError, f, [1, 2], A_ub=[[1, 2]], b_ub=[1, 2])
  402. assert_raises(ValueError, f, [1, 2], A_ub=[[1]], b_ub=[1])
  403. assert_raises(ValueError, f, [1, 2], A_eq=[[1, 2]], b_eq=[1, 2])
  404. assert_raises(ValueError, f, [1, 2], A_eq=[[1]], b_eq=[1])
  405. assert_raises(ValueError, f, [1, 2], A_eq=[1], b_eq=1)
  406. # this last check doesn't make sense for sparse presolve
  407. if ("_sparse_presolve" in self.options and
  408. self.options["_sparse_presolve"]):
  409. return
  410. # there aren't 3-D sparse matrices
  411. assert_raises(ValueError, f, [1, 2], A_ub=np.zeros((1, 1, 3)), b_eq=1)
  412. def test_sparse_constraints(self):
  413. # gh-13559: improve error message for sparse inputs when unsupported
  414. def f(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None):
  415. linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  416. method=self.method, options=self.options)
  417. np.random.seed(0)
  418. m = 100
  419. n = 150
  420. A_eq = scipy.sparse.rand(m, n, 0.5)
  421. x_valid = np.random.randn((n))
  422. c = np.random.randn((n))
  423. ub = x_valid + np.random.rand((n))
  424. lb = x_valid - np.random.rand((n))
  425. bounds = np.column_stack((lb, ub))
  426. b_eq = A_eq * x_valid
  427. if self.method in {'simplex', 'revised simplex'}:
  428. # simplex and revised simplex should raise error
  429. with assert_raises(ValueError, match=f"Method '{self.method}' "
  430. "does not support sparse constraint matrices."):
  431. linprog(c=c, A_eq=A_eq, b_eq=b_eq, bounds=bounds,
  432. method=self.method, options=self.options)
  433. else:
  434. # other methods should succeed
  435. options = {**self.options}
  436. if self.method in {'interior-point'}:
  437. options['sparse'] = True
  438. res = linprog(c=c, A_eq=A_eq, b_eq=b_eq, bounds=bounds,
  439. method=self.method, options=options)
  440. assert res.success
  441. def test_maxiter(self):
  442. # test iteration limit w/ Enzo example
  443. c = [4, 8, 3, 0, 0, 0]
  444. A = [
  445. [2, 5, 3, -1, 0, 0],
  446. [3, 2.5, 8, 0, -1, 0],
  447. [8, 10, 4, 0, 0, -1]]
  448. b = [185, 155, 600]
  449. np.random.seed(0)
  450. maxiter = 3
  451. res = linprog(c, A_eq=A, b_eq=b, method=self.method,
  452. options={"maxiter": maxiter})
  453. _assert_iteration_limit_reached(res, maxiter)
  454. assert_equal(res.nit, maxiter)
  455. def test_bounds_fixed(self):
  456. # Test fixed bounds (upper equal to lower)
  457. # If presolve option True, test if solution found in presolve (i.e.
  458. # number of iterations is 0).
  459. do_presolve = self.options.get('presolve', True)
  460. res = linprog([1], bounds=(1, 1),
  461. method=self.method, options=self.options)
  462. _assert_success(res, 1, 1)
  463. if do_presolve:
  464. assert_equal(res.nit, 0)
  465. res = linprog([1, 2, 3], bounds=[(5, 5), (-1, -1), (3, 3)],
  466. method=self.method, options=self.options)
  467. _assert_success(res, 12, [5, -1, 3])
  468. if do_presolve:
  469. assert_equal(res.nit, 0)
  470. res = linprog([1, 1], bounds=[(1, 1), (1, 3)],
  471. method=self.method, options=self.options)
  472. _assert_success(res, 2, [1, 1])
  473. if do_presolve:
  474. assert_equal(res.nit, 0)
  475. res = linprog([1, 1, 2], A_eq=[[1, 0, 0], [0, 1, 0]], b_eq=[1, 7],
  476. bounds=[(-5, 5), (0, 10), (3.5, 3.5)],
  477. method=self.method, options=self.options)
  478. _assert_success(res, 15, [1, 7, 3.5])
  479. if do_presolve:
  480. assert_equal(res.nit, 0)
  481. def test_bounds_infeasible(self):
  482. # Test ill-valued bounds (upper less than lower)
  483. # If presolve option True, test if solution found in presolve (i.e.
  484. # number of iterations is 0).
  485. do_presolve = self.options.get('presolve', True)
  486. res = linprog([1], bounds=(1, -2), method=self.method, options=self.options)
  487. _assert_infeasible(res)
  488. if do_presolve:
  489. assert_equal(res.nit, 0)
  490. res = linprog([1], bounds=[(1, -2)], method=self.method, options=self.options)
  491. _assert_infeasible(res)
  492. if do_presolve:
  493. assert_equal(res.nit, 0)
  494. res = linprog([1, 2, 3], bounds=[(5, 0), (1, 2), (3, 4)], method=self.method, options=self.options)
  495. _assert_infeasible(res)
  496. if do_presolve:
  497. assert_equal(res.nit, 0)
  498. def test_bounds_infeasible_2(self):
  499. # Test ill-valued bounds (lower inf, upper -inf)
  500. # If presolve option True, test if solution found in presolve (i.e.
  501. # number of iterations is 0).
  502. # For the simplex method, the cases do not result in an
  503. # infeasible status, but in a RuntimeWarning. This is a
  504. # consequence of having _presolve() take care of feasibility
  505. # checks. See issue gh-11618.
  506. do_presolve = self.options.get('presolve', True)
  507. simplex_without_presolve = not do_presolve and self.method == 'simplex'
  508. c = [1, 2, 3]
  509. bounds_1 = [(1, 2), (np.inf, np.inf), (3, 4)]
  510. bounds_2 = [(1, 2), (-np.inf, -np.inf), (3, 4)]
  511. if simplex_without_presolve:
  512. def g(c, bounds):
  513. res = linprog(c, bounds=bounds, method=self.method, options=self.options)
  514. return res
  515. with pytest.warns(RuntimeWarning):
  516. with pytest.raises(IndexError):
  517. g(c, bounds=bounds_1)
  518. with pytest.warns(RuntimeWarning):
  519. with pytest.raises(IndexError):
  520. g(c, bounds=bounds_2)
  521. else:
  522. res = linprog(c=c, bounds=bounds_1, method=self.method, options=self.options)
  523. _assert_infeasible(res)
  524. if do_presolve:
  525. assert_equal(res.nit, 0)
  526. res = linprog(c=c, bounds=bounds_2, method=self.method, options=self.options)
  527. _assert_infeasible(res)
  528. if do_presolve:
  529. assert_equal(res.nit, 0)
  530. def test_empty_constraint_1(self):
  531. c = [-1, -2]
  532. res = linprog(c, method=self.method, options=self.options)
  533. _assert_unbounded(res)
  534. def test_empty_constraint_2(self):
  535. c = [-1, 1, -1, 1]
  536. bounds = [(0, np.inf), (-np.inf, 0), (-1, 1), (-1, 1)]
  537. res = linprog(c, bounds=bounds,
  538. method=self.method, options=self.options)
  539. _assert_unbounded(res)
  540. # Unboundedness detected in presolve requires no iterations
  541. if self.options.get('presolve', True):
  542. assert_equal(res.nit, 0)
  543. def test_empty_constraint_3(self):
  544. c = [1, -1, 1, -1]
  545. bounds = [(0, np.inf), (-np.inf, 0), (-1, 1), (-1, 1)]
  546. res = linprog(c, bounds=bounds,
  547. method=self.method, options=self.options)
  548. _assert_success(res, desired_x=[0, 0, -1, 1], desired_fun=-2)
  549. def test_inequality_constraints(self):
  550. # Minimize linear function subject to linear inequality constraints.
  551. # http://www.dam.brown.edu/people/huiwang/classes/am121/Archive/simplex_121_c.pdf
  552. c = np.array([3, 2]) * -1 # maximize
  553. A_ub = [[2, 1],
  554. [1, 1],
  555. [1, 0]]
  556. b_ub = [10, 8, 4]
  557. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  558. method=self.method, options=self.options)
  559. _assert_success(res, desired_fun=-18, desired_x=[2, 6])
  560. def test_inequality_constraints2(self):
  561. # Minimize linear function subject to linear inequality constraints.
  562. # http://www.statslab.cam.ac.uk/~ff271/teaching/opt/notes/notes8.pdf
  563. # (dead link)
  564. c = [6, 3]
  565. A_ub = [[0, 3],
  566. [-1, -1],
  567. [-2, 1]]
  568. b_ub = [2, -1, -1]
  569. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  570. method=self.method, options=self.options)
  571. _assert_success(res, desired_fun=5, desired_x=[2 / 3, 1 / 3])
  572. def test_bounds_simple(self):
  573. c = [1, 2]
  574. bounds = (1, 2)
  575. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  576. method=self.method, options=self.options)
  577. _assert_success(res, desired_x=[1, 1])
  578. bounds = [(1, 2), (1, 2)]
  579. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  580. method=self.method, options=self.options)
  581. _assert_success(res, desired_x=[1, 1])
  582. def test_bounded_below_only_1(self):
  583. c = np.array([1.0])
  584. A_eq = np.array([[1.0]])
  585. b_eq = np.array([3.0])
  586. bounds = (1.0, None)
  587. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  588. method=self.method, options=self.options)
  589. _assert_success(res, desired_fun=3, desired_x=[3])
  590. def test_bounded_below_only_2(self):
  591. c = np.ones(3)
  592. A_eq = np.eye(3)
  593. b_eq = np.array([1, 2, 3])
  594. bounds = (0.5, np.inf)
  595. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  596. method=self.method, options=self.options)
  597. _assert_success(res, desired_x=b_eq, desired_fun=np.sum(b_eq))
  598. def test_bounded_above_only_1(self):
  599. c = np.array([1.0])
  600. A_eq = np.array([[1.0]])
  601. b_eq = np.array([3.0])
  602. bounds = (None, 10.0)
  603. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  604. method=self.method, options=self.options)
  605. _assert_success(res, desired_fun=3, desired_x=[3])
  606. def test_bounded_above_only_2(self):
  607. c = np.ones(3)
  608. A_eq = np.eye(3)
  609. b_eq = np.array([1, 2, 3])
  610. bounds = (-np.inf, 4)
  611. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  612. method=self.method, options=self.options)
  613. _assert_success(res, desired_x=b_eq, desired_fun=np.sum(b_eq))
  614. def test_bounds_infinity(self):
  615. c = np.ones(3)
  616. A_eq = np.eye(3)
  617. b_eq = np.array([1, 2, 3])
  618. bounds = (-np.inf, np.inf)
  619. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  620. method=self.method, options=self.options)
  621. _assert_success(res, desired_x=b_eq, desired_fun=np.sum(b_eq))
  622. def test_bounds_mixed(self):
  623. # Problem has one unbounded variable and
  624. # another with a negative lower bound.
  625. c = np.array([-1, 4]) * -1 # maximize
  626. A_ub = np.array([[-3, 1],
  627. [1, 2]], dtype=np.float64)
  628. b_ub = [6, 4]
  629. x0_bounds = (-np.inf, np.inf)
  630. x1_bounds = (-3, np.inf)
  631. bounds = (x0_bounds, x1_bounds)
  632. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  633. method=self.method, options=self.options)
  634. _assert_success(res, desired_fun=-80 / 7, desired_x=[-8 / 7, 18 / 7])
  635. def test_bounds_equal_but_infeasible(self):
  636. c = [-4, 1]
  637. A_ub = [[7, -2], [0, 1], [2, -2]]
  638. b_ub = [14, 0, 3]
  639. bounds = [(2, 2), (0, None)]
  640. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  641. method=self.method, options=self.options)
  642. _assert_infeasible(res)
  643. def test_bounds_equal_but_infeasible2(self):
  644. c = [-4, 1]
  645. A_eq = [[7, -2], [0, 1], [2, -2]]
  646. b_eq = [14, 0, 3]
  647. bounds = [(2, 2), (0, None)]
  648. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  649. method=self.method, options=self.options)
  650. _assert_infeasible(res)
  651. def test_bounds_equal_no_presolve(self):
  652. # There was a bug when a lower and upper bound were equal but
  653. # presolve was not on to eliminate the variable. The bound
  654. # was being converted to an equality constraint, but the bound
  655. # was not eliminated, leading to issues in postprocessing.
  656. c = [1, 2]
  657. A_ub = [[1, 2], [1.1, 2.2]]
  658. b_ub = [4, 8]
  659. bounds = [(1, 2), (2, 2)]
  660. o = {key: self.options[key] for key in self.options}
  661. o["presolve"] = False
  662. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  663. method=self.method, options=o)
  664. _assert_infeasible(res)
  665. def test_zero_column_1(self):
  666. m, n = 3, 4
  667. np.random.seed(0)
  668. c = np.random.rand(n)
  669. c[1] = 1
  670. A_eq = np.random.rand(m, n)
  671. A_eq[:, 1] = 0
  672. b_eq = np.random.rand(m)
  673. A_ub = [[1, 0, 1, 1]]
  674. b_ub = 3
  675. bounds = [(-10, 10), (-10, 10), (-10, None), (None, None)]
  676. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  677. method=self.method, options=self.options)
  678. _assert_success(res, desired_fun=-9.7087836730413404)
  679. def test_zero_column_2(self):
  680. if self.method in {'highs-ds', 'highs-ipm'}:
  681. # See upstream issue https://github.com/ERGO-Code/HiGHS/issues/648
  682. pytest.xfail()
  683. np.random.seed(0)
  684. m, n = 2, 4
  685. c = np.random.rand(n)
  686. c[1] = -1
  687. A_eq = np.random.rand(m, n)
  688. A_eq[:, 1] = 0
  689. b_eq = np.random.rand(m)
  690. A_ub = np.random.rand(m, n)
  691. A_ub[:, 1] = 0
  692. b_ub = np.random.rand(m)
  693. bounds = (None, None)
  694. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  695. method=self.method, options=self.options)
  696. _assert_unbounded(res)
  697. # Unboundedness detected in presolve
  698. if self.options.get('presolve', True) and "highs" not in self.method:
  699. # HiGHS detects unboundedness or infeasibility in presolve
  700. # It needs an iteration of simplex to be sure of unboundedness
  701. # Other solvers report that the problem is unbounded if feasible
  702. assert_equal(res.nit, 0)
  703. def test_zero_row_1(self):
  704. c = [1, 2, 3]
  705. A_eq = [[0, 0, 0], [1, 1, 1], [0, 0, 0]]
  706. b_eq = [0, 3, 0]
  707. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  708. method=self.method, options=self.options)
  709. _assert_success(res, desired_fun=3)
  710. def test_zero_row_2(self):
  711. A_ub = [[0, 0, 0], [1, 1, 1], [0, 0, 0]]
  712. b_ub = [0, 3, 0]
  713. c = [1, 2, 3]
  714. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  715. method=self.method, options=self.options)
  716. _assert_success(res, desired_fun=0)
  717. def test_zero_row_3(self):
  718. m, n = 2, 4
  719. c = np.random.rand(n)
  720. A_eq = np.random.rand(m, n)
  721. A_eq[0, :] = 0
  722. b_eq = np.random.rand(m)
  723. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  724. method=self.method, options=self.options)
  725. _assert_infeasible(res)
  726. # Infeasibility detected in presolve
  727. if self.options.get('presolve', True):
  728. assert_equal(res.nit, 0)
  729. def test_zero_row_4(self):
  730. m, n = 2, 4
  731. c = np.random.rand(n)
  732. A_ub = np.random.rand(m, n)
  733. A_ub[0, :] = 0
  734. b_ub = -np.random.rand(m)
  735. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  736. method=self.method, options=self.options)
  737. _assert_infeasible(res)
  738. # Infeasibility detected in presolve
  739. if self.options.get('presolve', True):
  740. assert_equal(res.nit, 0)
  741. def test_singleton_row_eq_1(self):
  742. c = [1, 1, 1, 2]
  743. A_eq = [[1, 0, 0, 0], [0, 2, 0, 0], [1, 0, 0, 0], [1, 1, 1, 1]]
  744. b_eq = [1, 2, 2, 4]
  745. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  746. method=self.method, options=self.options)
  747. _assert_infeasible(res)
  748. # Infeasibility detected in presolve
  749. if self.options.get('presolve', True):
  750. assert_equal(res.nit, 0)
  751. def test_singleton_row_eq_2(self):
  752. c = [1, 1, 1, 2]
  753. A_eq = [[1, 0, 0, 0], [0, 2, 0, 0], [1, 0, 0, 0], [1, 1, 1, 1]]
  754. b_eq = [1, 2, 1, 4]
  755. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  756. method=self.method, options=self.options)
  757. _assert_success(res, desired_fun=4)
  758. def test_singleton_row_ub_1(self):
  759. c = [1, 1, 1, 2]
  760. A_ub = [[1, 0, 0, 0], [0, 2, 0, 0], [-1, 0, 0, 0], [1, 1, 1, 1]]
  761. b_ub = [1, 2, -2, 4]
  762. bounds = [(None, None), (0, None), (0, None), (0, None)]
  763. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  764. method=self.method, options=self.options)
  765. _assert_infeasible(res)
  766. # Infeasibility detected in presolve
  767. if self.options.get('presolve', True):
  768. assert_equal(res.nit, 0)
  769. def test_singleton_row_ub_2(self):
  770. c = [1, 1, 1, 2]
  771. A_ub = [[1, 0, 0, 0], [0, 2, 0, 0], [-1, 0, 0, 0], [1, 1, 1, 1]]
  772. b_ub = [1, 2, -0.5, 4]
  773. bounds = [(None, None), (0, None), (0, None), (0, None)]
  774. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  775. method=self.method, options=self.options)
  776. _assert_success(res, desired_fun=0.5)
  777. def test_infeasible(self):
  778. # Test linprog response to an infeasible problem
  779. c = [-1, -1]
  780. A_ub = [[1, 0],
  781. [0, 1],
  782. [-1, -1]]
  783. b_ub = [2, 2, -5]
  784. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  785. method=self.method, options=self.options)
  786. _assert_infeasible(res)
  787. def test_infeasible_inequality_bounds(self):
  788. c = [1]
  789. A_ub = [[2]]
  790. b_ub = 4
  791. bounds = (5, 6)
  792. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  793. method=self.method, options=self.options)
  794. _assert_infeasible(res)
  795. # Infeasibility detected in presolve
  796. if self.options.get('presolve', True):
  797. assert_equal(res.nit, 0)
  798. def test_unbounded(self):
  799. # Test linprog response to an unbounded problem
  800. c = np.array([1, 1]) * -1 # maximize
  801. A_ub = [[-1, 1],
  802. [-1, -1]]
  803. b_ub = [-1, -2]
  804. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  805. method=self.method, options=self.options)
  806. _assert_unbounded(res)
  807. def test_unbounded_below_no_presolve_corrected(self):
  808. c = [1]
  809. bounds = [(None, 1)]
  810. o = {key: self.options[key] for key in self.options}
  811. o["presolve"] = False
  812. res = linprog(c=c, bounds=bounds,
  813. method=self.method,
  814. options=o)
  815. if self.method == "revised simplex":
  816. # Revised simplex has a special pathway for no constraints.
  817. assert_equal(res.status, 5)
  818. else:
  819. _assert_unbounded(res)
  820. def test_unbounded_no_nontrivial_constraints_1(self):
  821. """
  822. Test whether presolve pathway for detecting unboundedness after
  823. constraint elimination is working.
  824. """
  825. c = np.array([0, 0, 0, 1, -1, -1])
  826. A_ub = np.array([[1, 0, 0, 0, 0, 0],
  827. [0, 1, 0, 0, 0, 0],
  828. [0, 0, 0, 0, 0, -1]])
  829. b_ub = np.array([2, -2, 0])
  830. bounds = [(None, None), (None, None), (None, None),
  831. (-1, 1), (-1, 1), (0, None)]
  832. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  833. method=self.method, options=self.options)
  834. _assert_unbounded(res)
  835. if not self.method.lower().startswith("highs"):
  836. assert_equal(res.x[-1], np.inf)
  837. assert_equal(res.message[:36],
  838. "The problem is (trivially) unbounded")
  839. def test_unbounded_no_nontrivial_constraints_2(self):
  840. """
  841. Test whether presolve pathway for detecting unboundedness after
  842. constraint elimination is working.
  843. """
  844. c = np.array([0, 0, 0, 1, -1, 1])
  845. A_ub = np.array([[1, 0, 0, 0, 0, 0],
  846. [0, 1, 0, 0, 0, 0],
  847. [0, 0, 0, 0, 0, 1]])
  848. b_ub = np.array([2, -2, 0])
  849. bounds = [(None, None), (None, None), (None, None),
  850. (-1, 1), (-1, 1), (None, 0)]
  851. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  852. method=self.method, options=self.options)
  853. _assert_unbounded(res)
  854. if not self.method.lower().startswith("highs"):
  855. assert_equal(res.x[-1], -np.inf)
  856. assert_equal(res.message[:36],
  857. "The problem is (trivially) unbounded")
  858. def test_cyclic_recovery(self):
  859. # Test linprogs recovery from cycling using the Klee-Minty problem
  860. # Klee-Minty https://www.math.ubc.ca/~israel/m340/kleemin3.pdf
  861. c = np.array([100, 10, 1]) * -1 # maximize
  862. A_ub = [[1, 0, 0],
  863. [20, 1, 0],
  864. [200, 20, 1]]
  865. b_ub = [1, 100, 10000]
  866. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  867. method=self.method, options=self.options)
  868. _assert_success(res, desired_x=[0, 0, 10000], atol=5e-6, rtol=1e-7)
  869. def test_cyclic_bland(self):
  870. # Test the effect of Bland's rule on a cycling problem
  871. c = np.array([-10, 57, 9, 24.])
  872. A_ub = np.array([[0.5, -5.5, -2.5, 9],
  873. [0.5, -1.5, -0.5, 1],
  874. [1, 0, 0, 0]])
  875. b_ub = [0, 0, 1]
  876. # copy the existing options dictionary but change maxiter
  877. maxiter = 100
  878. o = {key: val for key, val in self.options.items()}
  879. o['maxiter'] = maxiter
  880. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  881. method=self.method, options=o)
  882. if self.method == 'simplex' and not self.options.get('bland'):
  883. # simplex cycles without Bland's rule
  884. _assert_iteration_limit_reached(res, o['maxiter'])
  885. else:
  886. # other methods, including simplex with Bland's rule, succeed
  887. _assert_success(res, desired_x=[1, 0, 1, 0])
  888. # note that revised simplex skips this test because it may or may not
  889. # cycle depending on the initial basis
  890. def test_remove_redundancy_infeasibility(self):
  891. # mostly a test of redundancy removal, which is carefully tested in
  892. # test__remove_redundancy.py
  893. m, n = 10, 10
  894. c = np.random.rand(n)
  895. A_eq = np.random.rand(m, n)
  896. b_eq = np.random.rand(m)
  897. A_eq[-1, :] = 2 * A_eq[-2, :]
  898. b_eq[-1] *= -1
  899. with suppress_warnings() as sup:
  900. sup.filter(OptimizeWarning, "A_eq does not appear...")
  901. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  902. method=self.method, options=self.options)
  903. _assert_infeasible(res)
  904. #################
  905. # General Tests #
  906. #################
  907. def test_nontrivial_problem(self):
  908. # Problem involves all constraint types,
  909. # negative resource limits, and rounding issues.
  910. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  911. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  912. method=self.method, options=self.options)
  913. _assert_success(res, desired_fun=f_star, desired_x=x_star)
  914. def test_lpgen_problem(self):
  915. # Test linprog with a rather large problem (400 variables,
  916. # 40 constraints) generated by https://gist.github.com/denis-bz/8647461
  917. A_ub, b_ub, c = lpgen_2d(20, 20)
  918. with suppress_warnings() as sup:
  919. sup.filter(OptimizeWarning, "Solving system with option 'sym_pos'")
  920. sup.filter(RuntimeWarning, "invalid value encountered")
  921. sup.filter(LinAlgWarning)
  922. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  923. method=self.method, options=self.options)
  924. _assert_success(res, desired_fun=-64.049494229)
  925. def test_network_flow(self):
  926. # A network flow problem with supply and demand at nodes
  927. # and with costs along directed edges.
  928. # https://www.princeton.edu/~rvdb/542/lectures/lec10.pdf
  929. c = [2, 4, 9, 11, 4, 3, 8, 7, 0, 15, 16, 18]
  930. n, p = -1, 1
  931. A_eq = [
  932. [n, n, p, 0, p, 0, 0, 0, 0, p, 0, 0],
  933. [p, 0, 0, p, 0, p, 0, 0, 0, 0, 0, 0],
  934. [0, 0, n, n, 0, 0, 0, 0, 0, 0, 0, 0],
  935. [0, 0, 0, 0, 0, 0, p, p, 0, 0, p, 0],
  936. [0, 0, 0, 0, n, n, n, 0, p, 0, 0, 0],
  937. [0, 0, 0, 0, 0, 0, 0, n, n, 0, 0, p],
  938. [0, 0, 0, 0, 0, 0, 0, 0, 0, n, n, n]]
  939. b_eq = [0, 19, -16, 33, 0, 0, -36]
  940. with suppress_warnings() as sup:
  941. sup.filter(LinAlgWarning)
  942. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  943. method=self.method, options=self.options)
  944. _assert_success(res, desired_fun=755, atol=1e-6, rtol=1e-7)
  945. def test_network_flow_limited_capacity(self):
  946. # A network flow problem with supply and demand at nodes
  947. # and with costs and capacities along directed edges.
  948. # http://blog.sommer-forst.de/2013/04/10/
  949. c = [2, 2, 1, 3, 1]
  950. bounds = [
  951. [0, 4],
  952. [0, 2],
  953. [0, 2],
  954. [0, 3],
  955. [0, 5]]
  956. n, p = -1, 1
  957. A_eq = [
  958. [n, n, 0, 0, 0],
  959. [p, 0, n, n, 0],
  960. [0, p, p, 0, n],
  961. [0, 0, 0, p, p]]
  962. b_eq = [-4, 0, 0, 4]
  963. with suppress_warnings() as sup:
  964. # this is an UmfpackWarning but I had trouble importing it
  965. if has_umfpack:
  966. sup.filter(UmfpackWarning)
  967. sup.filter(RuntimeWarning, "scipy.linalg.solve\nIll...")
  968. sup.filter(OptimizeWarning, "A_eq does not appear...")
  969. sup.filter(OptimizeWarning, "Solving system with option...")
  970. sup.filter(LinAlgWarning)
  971. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  972. method=self.method, options=self.options)
  973. _assert_success(res, desired_fun=14)
  974. def test_simplex_algorithm_wikipedia_example(self):
  975. # https://en.wikipedia.org/wiki/Simplex_algorithm#Example
  976. c = [-2, -3, -4]
  977. A_ub = [
  978. [3, 2, 1],
  979. [2, 5, 3]]
  980. b_ub = [10, 15]
  981. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  982. method=self.method, options=self.options)
  983. _assert_success(res, desired_fun=-20)
  984. def test_enzo_example(self):
  985. # https://github.com/scipy/scipy/issues/1779 lp2.py
  986. #
  987. # Translated from Octave code at:
  988. # http://www.ecs.shimane-u.ac.jp/~kyoshida/lpeng.htm
  989. # and placed under MIT licence by Enzo Michelangeli
  990. # with permission explicitly granted by the original author,
  991. # Prof. Kazunobu Yoshida
  992. c = [4, 8, 3, 0, 0, 0]
  993. A_eq = [
  994. [2, 5, 3, -1, 0, 0],
  995. [3, 2.5, 8, 0, -1, 0],
  996. [8, 10, 4, 0, 0, -1]]
  997. b_eq = [185, 155, 600]
  998. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  999. method=self.method, options=self.options)
  1000. _assert_success(res, desired_fun=317.5,
  1001. desired_x=[66.25, 0, 17.5, 0, 183.75, 0],
  1002. atol=6e-6, rtol=1e-7)
  1003. def test_enzo_example_b(self):
  1004. # rescued from https://github.com/scipy/scipy/pull/218
  1005. c = [2.8, 6.3, 10.8, -2.8, -6.3, -10.8]
  1006. A_eq = [[-1, -1, -1, 0, 0, 0],
  1007. [0, 0, 0, 1, 1, 1],
  1008. [1, 0, 0, 1, 0, 0],
  1009. [0, 1, 0, 0, 1, 0],
  1010. [0, 0, 1, 0, 0, 1]]
  1011. b_eq = [-0.5, 0.4, 0.3, 0.3, 0.3]
  1012. with suppress_warnings() as sup:
  1013. sup.filter(OptimizeWarning, "A_eq does not appear...")
  1014. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1015. method=self.method, options=self.options)
  1016. _assert_success(res, desired_fun=-1.77,
  1017. desired_x=[0.3, 0.2, 0.0, 0.0, 0.1, 0.3])
  1018. def test_enzo_example_c_with_degeneracy(self):
  1019. # rescued from https://github.com/scipy/scipy/pull/218
  1020. m = 20
  1021. c = -np.ones(m)
  1022. tmp = 2 * np.pi * np.arange(1, m + 1) / (m + 1)
  1023. A_eq = np.vstack((np.cos(tmp) - 1, np.sin(tmp)))
  1024. b_eq = [0, 0]
  1025. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1026. method=self.method, options=self.options)
  1027. _assert_success(res, desired_fun=0, desired_x=np.zeros(m))
  1028. def test_enzo_example_c_with_unboundedness(self):
  1029. # rescued from https://github.com/scipy/scipy/pull/218
  1030. m = 50
  1031. c = -np.ones(m)
  1032. tmp = 2 * np.pi * np.arange(m) / (m + 1)
  1033. A_eq = np.vstack((np.cos(tmp) - 1, np.sin(tmp)))
  1034. b_eq = [0, 0]
  1035. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1036. method=self.method, options=self.options)
  1037. _assert_unbounded(res)
  1038. def test_enzo_example_c_with_infeasibility(self):
  1039. # rescued from https://github.com/scipy/scipy/pull/218
  1040. m = 50
  1041. c = -np.ones(m)
  1042. tmp = 2 * np.pi * np.arange(m) / (m + 1)
  1043. A_eq = np.vstack((np.cos(tmp) - 1, np.sin(tmp)))
  1044. b_eq = [1, 1]
  1045. o = {key: self.options[key] for key in self.options}
  1046. o["presolve"] = False
  1047. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1048. method=self.method, options=o)
  1049. _assert_infeasible(res)
  1050. def test_basic_artificial_vars(self):
  1051. # Problem is chosen to test two phase simplex methods when at the end
  1052. # of phase 1 some artificial variables remain in the basis.
  1053. # Also, for `method='simplex'`, the row in the tableau corresponding
  1054. # with the artificial variables is not all zero.
  1055. c = np.array([-0.1, -0.07, 0.004, 0.004, 0.004, 0.004])
  1056. A_ub = np.array([[1.0, 0, 0, 0, 0, 0], [-1.0, 0, 0, 0, 0, 0],
  1057. [0, -1.0, 0, 0, 0, 0], [0, 1.0, 0, 0, 0, 0],
  1058. [1.0, 1.0, 0, 0, 0, 0]])
  1059. b_ub = np.array([3.0, 3.0, 3.0, 3.0, 20.0])
  1060. A_eq = np.array([[1.0, 0, -1, 1, -1, 1], [0, -1.0, -1, 1, -1, 1]])
  1061. b_eq = np.array([0, 0])
  1062. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1063. method=self.method, options=self.options)
  1064. _assert_success(res, desired_fun=0, desired_x=np.zeros_like(c),
  1065. atol=2e-6)
  1066. def test_optimize_result(self):
  1067. # check all fields in OptimizeResult
  1068. c, A_ub, b_ub, A_eq, b_eq, bounds = very_random_gen(0)
  1069. res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
  1070. bounds=bounds, method=self.method, options=self.options)
  1071. assert_(res.success)
  1072. assert_(res.nit)
  1073. assert_(not res.status)
  1074. if 'highs' not in self.method:
  1075. # HiGHS status/message tested separately
  1076. assert_(res.message == "Optimization terminated successfully.")
  1077. assert_allclose(c @ res.x, res.fun)
  1078. assert_allclose(b_eq - A_eq @ res.x, res.con, atol=1e-11)
  1079. assert_allclose(b_ub - A_ub @ res.x, res.slack, atol=1e-11)
  1080. for key in ['eqlin', 'ineqlin', 'lower', 'upper']:
  1081. if key in res.keys():
  1082. assert isinstance(res[key]['marginals'], np.ndarray)
  1083. assert isinstance(res[key]['residual'], np.ndarray)
  1084. #################
  1085. # Bug Fix Tests #
  1086. #################
  1087. def test_bug_5400(self):
  1088. # https://github.com/scipy/scipy/issues/5400
  1089. bounds = [
  1090. (0, None),
  1091. (0, 100), (0, 100), (0, 100), (0, 100), (0, 100), (0, 100),
  1092. (0, 900), (0, 900), (0, 900), (0, 900), (0, 900), (0, 900),
  1093. (0, None), (0, None), (0, None), (0, None), (0, None), (0, None)]
  1094. f = 1 / 9
  1095. g = -1e4
  1096. h = -3.1
  1097. A_ub = np.array([
  1098. [1, -2.99, 0, 0, -3, 0, 0, 0, -1, -1, 0, -1, -1, 1, 1, 0, 0, 0, 0],
  1099. [1, 0, -2.9, h, 0, -3, 0, -1, 0, 0, -1, 0, -1, 0, 0, 1, 1, 0, 0],
  1100. [1, 0, 0, h, 0, 0, -3, -1, -1, 0, -1, -1, 0, 0, 0, 0, 0, 1, 1],
  1101. [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1102. [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1103. [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1104. [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1105. [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1106. [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1107. [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1108. [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1109. [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1110. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
  1111. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
  1112. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
  1113. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0],
  1114. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0],
  1115. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0],
  1116. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0],
  1117. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0],
  1118. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1],
  1119. [0, 1.99, -1, -1, 0, 0, 0, -1, f, f, 0, 0, 0, g, 0, 0, 0, 0, 0],
  1120. [0, 0, 0, 0, 2, -1, -1, 0, 0, 0, -1, f, f, 0, g, 0, 0, 0, 0],
  1121. [0, -1, 1.9, 2.1, 0, 0, 0, f, -1, -1, 0, 0, 0, 0, 0, g, 0, 0, 0],
  1122. [0, 0, 0, 0, -1, 2, -1, 0, 0, 0, f, -1, f, 0, 0, 0, g, 0, 0],
  1123. [0, -1, -1, 2.1, 0, 0, 0, f, f, -1, 0, 0, 0, 0, 0, 0, 0, g, 0],
  1124. [0, 0, 0, 0, -1, -1, 2, 0, 0, 0, f, f, -1, 0, 0, 0, 0, 0, g]])
  1125. b_ub = np.array([
  1126. 0.0, 0, 0, 100, 100, 100, 100, 100, 100, 900, 900, 900, 900, 900,
  1127. 900, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
  1128. c = np.array([-1.0, 1, 1, 1, 1, 1, 1, 1, 1,
  1129. 1, 1, 1, 1, 0, 0, 0, 0, 0, 0])
  1130. with suppress_warnings() as sup:
  1131. sup.filter(OptimizeWarning,
  1132. "Solving system with option 'sym_pos'")
  1133. sup.filter(RuntimeWarning, "invalid value encountered")
  1134. sup.filter(LinAlgWarning)
  1135. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1136. method=self.method, options=self.options)
  1137. _assert_success(res, desired_fun=-106.63507541835018)
  1138. def test_bug_6139(self):
  1139. # linprog(method='simplex') fails to find a basic feasible solution
  1140. # if phase 1 pseudo-objective function is outside the provided tol.
  1141. # https://github.com/scipy/scipy/issues/6139
  1142. # Note: This is not strictly a bug as the default tolerance determines
  1143. # if a result is "close enough" to zero and should not be expected
  1144. # to work for all cases.
  1145. c = np.array([1, 1, 1])
  1146. A_eq = np.array([[1., 0., 0.], [-1000., 0., - 1000.]])
  1147. b_eq = np.array([5.00000000e+00, -1.00000000e+04])
  1148. A_ub = -np.array([[0., 1000000., 1010000.]])
  1149. b_ub = -np.array([10000000.])
  1150. bounds = (None, None)
  1151. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1152. method=self.method, options=self.options)
  1153. _assert_success(res, desired_fun=14.95,
  1154. desired_x=np.array([5, 4.95, 5]))
  1155. def test_bug_6690(self):
  1156. # linprog simplex used to violate bound constraint despite reporting
  1157. # success.
  1158. # https://github.com/scipy/scipy/issues/6690
  1159. A_eq = np.array([[0, 0, 0, 0.93, 0, 0.65, 0, 0, 0.83, 0]])
  1160. b_eq = np.array([0.9626])
  1161. A_ub = np.array([
  1162. [0, 0, 0, 1.18, 0, 0, 0, -0.2, 0, -0.22],
  1163. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  1164. [0, 0, 0, 0.43, 0, 0, 0, 0, 0, 0],
  1165. [0, -1.22, -0.25, 0, 0, 0, -2.06, 0, 0, 1.37],
  1166. [0, 0, 0, 0, 0, 0, 0, -0.25, 0, 0]
  1167. ])
  1168. b_ub = np.array([0.615, 0, 0.172, -0.869, -0.022])
  1169. bounds = np.array([
  1170. [-0.84, -0.97, 0.34, 0.4, -0.33, -0.74, 0.47, 0.09, -1.45, -0.73],
  1171. [0.37, 0.02, 2.86, 0.86, 1.18, 0.5, 1.76, 0.17, 0.32, -0.15]
  1172. ]).T
  1173. c = np.array([
  1174. -1.64, 0.7, 1.8, -1.06, -1.16, 0.26, 2.13, 1.53, 0.66, 0.28
  1175. ])
  1176. with suppress_warnings() as sup:
  1177. if has_umfpack:
  1178. sup.filter(UmfpackWarning)
  1179. sup.filter(OptimizeWarning,
  1180. "Solving system with option 'cholesky'")
  1181. sup.filter(OptimizeWarning, "Solving system with option 'sym_pos'")
  1182. sup.filter(RuntimeWarning, "invalid value encountered")
  1183. sup.filter(LinAlgWarning)
  1184. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1185. method=self.method, options=self.options)
  1186. desired_fun = -1.19099999999
  1187. desired_x = np.array([0.3700, -0.9700, 0.3400, 0.4000, 1.1800,
  1188. 0.5000, 0.4700, 0.0900, 0.3200, -0.7300])
  1189. _assert_success(res, desired_fun=desired_fun, desired_x=desired_x)
  1190. # Add small tol value to ensure arrays are less than or equal.
  1191. atol = 1e-6
  1192. assert_array_less(bounds[:, 0] - atol, res.x)
  1193. assert_array_less(res.x, bounds[:, 1] + atol)
  1194. def test_bug_7044(self):
  1195. # linprog simplex failed to "identify correct constraints" (?)
  1196. # leading to a non-optimal solution if A is rank-deficient.
  1197. # https://github.com/scipy/scipy/issues/7044
  1198. A_eq, b_eq, c, _, _ = magic_square(3)
  1199. with suppress_warnings() as sup:
  1200. sup.filter(OptimizeWarning, "A_eq does not appear...")
  1201. sup.filter(RuntimeWarning, "invalid value encountered")
  1202. sup.filter(LinAlgWarning)
  1203. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1204. method=self.method, options=self.options)
  1205. desired_fun = 1.730550597
  1206. _assert_success(res, desired_fun=desired_fun)
  1207. assert_allclose(A_eq.dot(res.x), b_eq)
  1208. assert_array_less(np.zeros(res.x.size) - 1e-5, res.x)
  1209. def test_bug_7237(self):
  1210. # https://github.com/scipy/scipy/issues/7237
  1211. # linprog simplex "explodes" when the pivot value is very
  1212. # close to zero.
  1213. c = np.array([-1, 0, 0, 0, 0, 0, 0, 0, 0])
  1214. A_ub = np.array([
  1215. [1., -724., 911., -551., -555., -896., 478., -80., -293.],
  1216. [1., 566., 42., 937., 233., 883., 392., -909., 57.],
  1217. [1., -208., -894., 539., 321., 532., -924., 942., 55.],
  1218. [1., 857., -859., 83., 462., -265., -971., 826., 482.],
  1219. [1., 314., -424., 245., -424., 194., -443., -104., -429.],
  1220. [1., 540., 679., 361., 149., -827., 876., 633., 302.],
  1221. [0., -1., -0., -0., -0., -0., -0., -0., -0.],
  1222. [0., -0., -1., -0., -0., -0., -0., -0., -0.],
  1223. [0., -0., -0., -1., -0., -0., -0., -0., -0.],
  1224. [0., -0., -0., -0., -1., -0., -0., -0., -0.],
  1225. [0., -0., -0., -0., -0., -1., -0., -0., -0.],
  1226. [0., -0., -0., -0., -0., -0., -1., -0., -0.],
  1227. [0., -0., -0., -0., -0., -0., -0., -1., -0.],
  1228. [0., -0., -0., -0., -0., -0., -0., -0., -1.],
  1229. [0., 1., 0., 0., 0., 0., 0., 0., 0.],
  1230. [0., 0., 1., 0., 0., 0., 0., 0., 0.],
  1231. [0., 0., 0., 1., 0., 0., 0., 0., 0.],
  1232. [0., 0., 0., 0., 1., 0., 0., 0., 0.],
  1233. [0., 0., 0., 0., 0., 1., 0., 0., 0.],
  1234. [0., 0., 0., 0., 0., 0., 1., 0., 0.],
  1235. [0., 0., 0., 0., 0., 0., 0., 1., 0.],
  1236. [0., 0., 0., 0., 0., 0., 0., 0., 1.]
  1237. ])
  1238. b_ub = np.array([
  1239. 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
  1240. 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.])
  1241. A_eq = np.array([[0., 1., 1., 1., 1., 1., 1., 1., 1.]])
  1242. b_eq = np.array([[1.]])
  1243. bounds = [(None, None)] * 9
  1244. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1245. method=self.method, options=self.options)
  1246. _assert_success(res, desired_fun=108.568535, atol=1e-6)
  1247. def test_bug_8174(self):
  1248. # https://github.com/scipy/scipy/issues/8174
  1249. # The simplex method sometimes "explodes" if the pivot value is very
  1250. # close to zero.
  1251. A_ub = np.array([
  1252. [22714, 1008, 13380, -2713.5, -1116],
  1253. [-4986, -1092, -31220, 17386.5, 684],
  1254. [-4986, 0, 0, -2713.5, 0],
  1255. [22714, 0, 0, 17386.5, 0]])
  1256. b_ub = np.zeros(A_ub.shape[0])
  1257. c = -np.ones(A_ub.shape[1])
  1258. bounds = [(0, 1)] * A_ub.shape[1]
  1259. with suppress_warnings() as sup:
  1260. sup.filter(RuntimeWarning, "invalid value encountered")
  1261. sup.filter(LinAlgWarning)
  1262. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1263. method=self.method, options=self.options)
  1264. if self.options.get('tol', 1e-9) < 1e-10 and self.method == 'simplex':
  1265. _assert_unable_to_find_basic_feasible_sol(res)
  1266. else:
  1267. _assert_success(res, desired_fun=-2.0080717488789235, atol=1e-6)
  1268. def test_bug_8174_2(self):
  1269. # Test supplementary example from issue 8174.
  1270. # https://github.com/scipy/scipy/issues/8174
  1271. # https://stackoverflow.com/questions/47717012/linprog-in-scipy-optimize-checking-solution
  1272. c = np.array([1, 0, 0, 0, 0, 0, 0])
  1273. A_ub = -np.identity(7)
  1274. b_ub = np.array([[-2], [-2], [-2], [-2], [-2], [-2], [-2]])
  1275. A_eq = np.array([
  1276. [1, 1, 1, 1, 1, 1, 0],
  1277. [0.3, 1.3, 0.9, 0, 0, 0, -1],
  1278. [0.3, 0, 0, 0, 0, 0, -2/3],
  1279. [0, 0.65, 0, 0, 0, 0, -1/15],
  1280. [0, 0, 0.3, 0, 0, 0, -1/15]
  1281. ])
  1282. b_eq = np.array([[100], [0], [0], [0], [0]])
  1283. with suppress_warnings() as sup:
  1284. if has_umfpack:
  1285. sup.filter(UmfpackWarning)
  1286. sup.filter(OptimizeWarning, "A_eq does not appear...")
  1287. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1288. method=self.method, options=self.options)
  1289. _assert_success(res, desired_fun=43.3333333331385)
  1290. def test_bug_8561(self):
  1291. # Test that pivot row is chosen correctly when using Bland's rule
  1292. # This was originally written for the simplex method with
  1293. # Bland's rule only, but it doesn't hurt to test all methods/options
  1294. # https://github.com/scipy/scipy/issues/8561
  1295. c = np.array([7, 0, -4, 1.5, 1.5])
  1296. A_ub = np.array([
  1297. [4, 5.5, 1.5, 1.0, -3.5],
  1298. [1, -2.5, -2, 2.5, 0.5],
  1299. [3, -0.5, 4, -12.5, -7],
  1300. [-1, 4.5, 2, -3.5, -2],
  1301. [5.5, 2, -4.5, -1, 9.5]])
  1302. b_ub = np.array([0, 0, 0, 0, 1])
  1303. res = linprog(c, A_ub=A_ub, b_ub=b_ub, options=self.options,
  1304. method=self.method)
  1305. _assert_success(res, desired_x=[0, 0, 19, 16/3, 29/3])
  1306. def test_bug_8662(self):
  1307. # linprog simplex used to report incorrect optimal results
  1308. # https://github.com/scipy/scipy/issues/8662
  1309. c = [-10, 10, 6, 3]
  1310. A_ub = [[8, -8, -4, 6],
  1311. [-8, 8, 4, -6],
  1312. [-4, 4, 8, -4],
  1313. [3, -3, -3, -10]]
  1314. b_ub = [9, -9, -9, -4]
  1315. bounds = [(0, None), (0, None), (0, None), (0, None)]
  1316. desired_fun = 36.0000000000
  1317. with suppress_warnings() as sup:
  1318. if has_umfpack:
  1319. sup.filter(UmfpackWarning)
  1320. sup.filter(RuntimeWarning, "invalid value encountered")
  1321. sup.filter(LinAlgWarning)
  1322. res1 = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1323. method=self.method, options=self.options)
  1324. # Set boundary condition as a constraint
  1325. A_ub.append([0, 0, -1, 0])
  1326. b_ub.append(0)
  1327. bounds[2] = (None, None)
  1328. with suppress_warnings() as sup:
  1329. if has_umfpack:
  1330. sup.filter(UmfpackWarning)
  1331. sup.filter(RuntimeWarning, "invalid value encountered")
  1332. sup.filter(LinAlgWarning)
  1333. res2 = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1334. method=self.method, options=self.options)
  1335. rtol = 1e-5
  1336. _assert_success(res1, desired_fun=desired_fun, rtol=rtol)
  1337. _assert_success(res2, desired_fun=desired_fun, rtol=rtol)
  1338. def test_bug_8663(self):
  1339. # exposed a bug in presolve
  1340. # https://github.com/scipy/scipy/issues/8663
  1341. c = [1, 5]
  1342. A_eq = [[0, -7]]
  1343. b_eq = [-6]
  1344. bounds = [(0, None), (None, None)]
  1345. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1346. method=self.method, options=self.options)
  1347. _assert_success(res, desired_x=[0, 6./7], desired_fun=5*6./7)
  1348. def test_bug_8664(self):
  1349. # interior-point has trouble with this when presolve is off
  1350. # tested for interior-point with presolve off in TestLinprogIPSpecific
  1351. # https://github.com/scipy/scipy/issues/8664
  1352. c = [4]
  1353. A_ub = [[2], [5]]
  1354. b_ub = [4, 4]
  1355. A_eq = [[0], [-8], [9]]
  1356. b_eq = [3, 2, 10]
  1357. with suppress_warnings() as sup:
  1358. sup.filter(RuntimeWarning)
  1359. sup.filter(OptimizeWarning, "Solving system with option...")
  1360. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1361. method=self.method, options=self.options)
  1362. _assert_infeasible(res)
  1363. def test_bug_8973(self):
  1364. """
  1365. Test whether bug described at:
  1366. https://github.com/scipy/scipy/issues/8973
  1367. was fixed.
  1368. """
  1369. c = np.array([0, 0, 0, 1, -1])
  1370. A_ub = np.array([[1, 0, 0, 0, 0], [0, 1, 0, 0, 0]])
  1371. b_ub = np.array([2, -2])
  1372. bounds = [(None, None), (None, None), (None, None), (-1, 1), (-1, 1)]
  1373. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1374. method=self.method, options=self.options)
  1375. # solution vector x is not unique
  1376. _assert_success(res, desired_fun=-2)
  1377. # HiGHS IPM had an issue where the following wasn't true!
  1378. assert_equal(c @ res.x, res.fun)
  1379. def test_bug_8973_2(self):
  1380. """
  1381. Additional test for:
  1382. https://github.com/scipy/scipy/issues/8973
  1383. suggested in
  1384. https://github.com/scipy/scipy/pull/8985
  1385. review by @antonior92
  1386. """
  1387. c = np.zeros(1)
  1388. A_ub = np.array([[1]])
  1389. b_ub = np.array([-2])
  1390. bounds = (None, None)
  1391. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1392. method=self.method, options=self.options)
  1393. _assert_success(res, desired_x=[-2], desired_fun=0)
  1394. def test_bug_10124(self):
  1395. """
  1396. Test for linprog docstring problem
  1397. 'disp'=True caused revised simplex failure
  1398. """
  1399. c = np.zeros(1)
  1400. A_ub = np.array([[1]])
  1401. b_ub = np.array([-2])
  1402. bounds = (None, None)
  1403. c = [-1, 4]
  1404. A_ub = [[-3, 1], [1, 2]]
  1405. b_ub = [6, 4]
  1406. bounds = [(None, None), (-3, None)]
  1407. o = {"disp": True}
  1408. o.update(self.options)
  1409. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1410. method=self.method, options=o)
  1411. _assert_success(res, desired_x=[10, -3], desired_fun=-22)
  1412. def test_bug_10349(self):
  1413. """
  1414. Test for redundancy removal tolerance issue
  1415. https://github.com/scipy/scipy/issues/10349
  1416. """
  1417. A_eq = np.array([[1, 1, 0, 0, 0, 0],
  1418. [0, 0, 1, 1, 0, 0],
  1419. [0, 0, 0, 0, 1, 1],
  1420. [1, 0, 1, 0, 0, 0],
  1421. [0, 0, 0, 1, 1, 0],
  1422. [0, 1, 0, 0, 0, 1]])
  1423. b_eq = np.array([221, 210, 10, 141, 198, 102])
  1424. c = np.concatenate((0, 1, np.zeros(4)), axis=None)
  1425. with suppress_warnings() as sup:
  1426. sup.filter(OptimizeWarning, "A_eq does not appear...")
  1427. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1428. method=self.method, options=self.options)
  1429. _assert_success(res, desired_x=[129, 92, 12, 198, 0, 10], desired_fun=92)
  1430. @pytest.mark.skipif(sys.platform == 'darwin',
  1431. reason=("Failing on some local macOS builds, "
  1432. "see gh-13846"))
  1433. def test_bug_10466(self):
  1434. """
  1435. Test that autoscale fixes poorly-scaled problem
  1436. """
  1437. c = [-8., -0., -8., -0., -8., -0., -0., -0., -0., -0., -0., -0., -0.]
  1438. A_eq = [[1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
  1439. [0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
  1440. [0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0.],
  1441. [1., 0., 1., 0., 1., 0., -1., 0., 0., 0., 0., 0., 0.],
  1442. [1., 0., 1., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0.],
  1443. [1., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
  1444. [1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
  1445. [1., 0., 1., 0., 1., 0., 0., 0., 0., 0., 1., 0., 0.],
  1446. [0., 0., 1., 0., 1., 0., 0., 0., 0., 0., 0., 1., 0.],
  1447. [0., 0., 1., 0., 1., 0., 0., 0., 0., 0., 0., 0., 1.]]
  1448. b_eq = [3.14572800e+08, 4.19430400e+08, 5.24288000e+08,
  1449. 1.00663296e+09, 1.07374182e+09, 1.07374182e+09,
  1450. 1.07374182e+09, 1.07374182e+09, 1.07374182e+09,
  1451. 1.07374182e+09]
  1452. o = {}
  1453. # HiGHS methods don't use autoscale option
  1454. if not self.method.startswith("highs"):
  1455. o = {"autoscale": True}
  1456. o.update(self.options)
  1457. with suppress_warnings() as sup:
  1458. sup.filter(OptimizeWarning, "Solving system with option...")
  1459. if has_umfpack:
  1460. sup.filter(UmfpackWarning)
  1461. sup.filter(RuntimeWarning, "scipy.linalg.solve\nIll...")
  1462. sup.filter(RuntimeWarning, "divide by zero encountered...")
  1463. sup.filter(RuntimeWarning, "overflow encountered...")
  1464. sup.filter(RuntimeWarning, "invalid value encountered...")
  1465. sup.filter(LinAlgWarning, "Ill-conditioned matrix...")
  1466. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1467. method=self.method, options=o)
  1468. assert_allclose(res.fun, -8589934560)
  1469. #########################
  1470. # Method-specific Tests #
  1471. #########################
  1472. @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  1473. class LinprogSimplexTests(LinprogCommonTests):
  1474. method = "simplex"
  1475. @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  1476. class LinprogIPTests(LinprogCommonTests):
  1477. method = "interior-point"
  1478. def test_bug_10466(self):
  1479. pytest.skip("Test is failing, but solver is deprecated.")
  1480. @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  1481. class LinprogRSTests(LinprogCommonTests):
  1482. method = "revised simplex"
  1483. # Revised simplex does not reliably solve these problems.
  1484. # Failure is intermittent due to the random choice of elements to complete
  1485. # the basis after phase 1 terminates. In any case, linprog exists
  1486. # gracefully, reporting numerical difficulties. I do not think this should
  1487. # prevent revised simplex from being merged, as it solves the problems
  1488. # most of the time and solves a broader range of problems than the existing
  1489. # simplex implementation.
  1490. # I believe that the root cause is the same for all three and that this
  1491. # same issue prevents revised simplex from solving many other problems
  1492. # reliably. Somehow the pivoting rule allows the algorithm to pivot into
  1493. # a singular basis. I haven't been able to find a reference that
  1494. # acknowledges this possibility, suggesting that there is a bug. On the
  1495. # other hand, the pivoting rule is quite simple, and I can't find a
  1496. # mistake, which suggests that this is a possibility with the pivoting
  1497. # rule. Hopefully, a better pivoting rule will fix the issue.
  1498. def test_bug_5400(self):
  1499. pytest.skip("Intermittent failure acceptable.")
  1500. def test_bug_8662(self):
  1501. pytest.skip("Intermittent failure acceptable.")
  1502. def test_network_flow(self):
  1503. pytest.skip("Intermittent failure acceptable.")
  1504. class LinprogHiGHSTests(LinprogCommonTests):
  1505. def test_callback(self):
  1506. # this is the problem from test_callback
  1507. cb = lambda res: None
  1508. c = np.array([-3, -2])
  1509. A_ub = [[2, 1], [1, 1], [1, 0]]
  1510. b_ub = [10, 8, 4]
  1511. assert_raises(NotImplementedError, linprog, c, A_ub=A_ub, b_ub=b_ub,
  1512. callback=cb, method=self.method)
  1513. res = linprog(c, A_ub=A_ub, b_ub=b_ub, method=self.method)
  1514. _assert_success(res, desired_fun=-18.0, desired_x=[2, 6])
  1515. @pytest.mark.parametrize("options",
  1516. [{"maxiter": -1},
  1517. {"disp": -1},
  1518. {"presolve": -1},
  1519. {"time_limit": -1},
  1520. {"dual_feasibility_tolerance": -1},
  1521. {"primal_feasibility_tolerance": -1},
  1522. {"ipm_optimality_tolerance": -1},
  1523. {"simplex_dual_edge_weight_strategy": "ekki"},
  1524. ])
  1525. def test_invalid_option_values(self, options):
  1526. def f(options):
  1527. linprog(1, method=self.method, options=options)
  1528. options.update(self.options)
  1529. assert_warns(OptimizeWarning, f, options=options)
  1530. def test_crossover(self):
  1531. A_eq, b_eq, c, _, _ = magic_square(4)
  1532. bounds = (0, 1)
  1533. res = linprog(c, A_eq=A_eq, b_eq=b_eq,
  1534. bounds=bounds, method=self.method, options=self.options)
  1535. # there should be nonzero crossover iterations for IPM (only)
  1536. assert_equal(res.crossover_nit == 0, self.method != "highs-ipm")
  1537. def test_marginals(self):
  1538. # Ensure lagrange multipliers are correct by comparing the derivative
  1539. # w.r.t. b_ub/b_eq/ub/lb to the reported duals.
  1540. c, A_ub, b_ub, A_eq, b_eq, bounds = very_random_gen(seed=0)
  1541. res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
  1542. bounds=bounds, method=self.method, options=self.options)
  1543. lb, ub = bounds.T
  1544. # sensitivity w.r.t. b_ub
  1545. def f_bub(x):
  1546. return linprog(c, A_ub, x, A_eq, b_eq, bounds,
  1547. method=self.method).fun
  1548. dfdbub = approx_derivative(f_bub, b_ub, method='3-point', f0=res.fun)
  1549. assert_allclose(res.ineqlin.marginals, dfdbub)
  1550. # sensitivity w.r.t. b_eq
  1551. def f_beq(x):
  1552. return linprog(c, A_ub, b_ub, A_eq, x, bounds,
  1553. method=self.method).fun
  1554. dfdbeq = approx_derivative(f_beq, b_eq, method='3-point', f0=res.fun)
  1555. assert_allclose(res.eqlin.marginals, dfdbeq)
  1556. # sensitivity w.r.t. lb
  1557. def f_lb(x):
  1558. bounds = np.array([x, ub]).T
  1559. return linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1560. method=self.method).fun
  1561. with np.errstate(invalid='ignore'):
  1562. # approx_derivative has trouble where lb is infinite
  1563. dfdlb = approx_derivative(f_lb, lb, method='3-point', f0=res.fun)
  1564. dfdlb[~np.isfinite(lb)] = 0
  1565. assert_allclose(res.lower.marginals, dfdlb)
  1566. # sensitivity w.r.t. ub
  1567. def f_ub(x):
  1568. bounds = np.array([lb, x]).T
  1569. return linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1570. method=self.method).fun
  1571. with np.errstate(invalid='ignore'):
  1572. dfdub = approx_derivative(f_ub, ub, method='3-point', f0=res.fun)
  1573. dfdub[~np.isfinite(ub)] = 0
  1574. assert_allclose(res.upper.marginals, dfdub)
  1575. def test_dual_feasibility(self):
  1576. # Ensure solution is dual feasible using marginals
  1577. c, A_ub, b_ub, A_eq, b_eq, bounds = very_random_gen(seed=42)
  1578. res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
  1579. bounds=bounds, method=self.method, options=self.options)
  1580. # KKT dual feasibility equation from Theorem 1 from
  1581. # http://www.personal.psu.edu/cxg286/LPKKT.pdf
  1582. resid = (-c + A_ub.T @ res.ineqlin.marginals +
  1583. A_eq.T @ res.eqlin.marginals +
  1584. res.upper.marginals +
  1585. res.lower.marginals)
  1586. assert_allclose(resid, 0, atol=1e-12)
  1587. def test_complementary_slackness(self):
  1588. # Ensure that the complementary slackness condition is satisfied.
  1589. c, A_ub, b_ub, A_eq, b_eq, bounds = very_random_gen(seed=42)
  1590. res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
  1591. bounds=bounds, method=self.method, options=self.options)
  1592. # KKT complementary slackness equation from Theorem 1 from
  1593. # http://www.personal.psu.edu/cxg286/LPKKT.pdf modified for
  1594. # non-zero RHS
  1595. assert np.allclose(res.ineqlin.marginals @ (b_ub - A_ub @ res.x), 0)
  1596. ################################
  1597. # Simplex Option-Specific Tests#
  1598. ################################
  1599. class TestLinprogSimplexDefault(LinprogSimplexTests):
  1600. def setup_method(self):
  1601. self.options = {}
  1602. def test_bug_5400(self):
  1603. pytest.skip("Simplex fails on this problem.")
  1604. def test_bug_7237_low_tol(self):
  1605. # Fails if the tolerance is too strict. Here, we test that
  1606. # even if the solution is wrong, the appropriate error is raised.
  1607. pytest.skip("Simplex fails on this problem.")
  1608. def test_bug_8174_low_tol(self):
  1609. # Fails if the tolerance is too strict. Here, we test that
  1610. # even if the solution is wrong, the appropriate warning is issued.
  1611. self.options.update({'tol': 1e-12})
  1612. with pytest.warns(OptimizeWarning):
  1613. super().test_bug_8174()
  1614. class TestLinprogSimplexBland(LinprogSimplexTests):
  1615. def setup_method(self):
  1616. self.options = {'bland': True}
  1617. def test_bug_5400(self):
  1618. pytest.skip("Simplex fails on this problem.")
  1619. def test_bug_8174_low_tol(self):
  1620. # Fails if the tolerance is too strict. Here, we test that
  1621. # even if the solution is wrong, the appropriate error is raised.
  1622. self.options.update({'tol': 1e-12})
  1623. with pytest.raises(AssertionError):
  1624. with pytest.warns(OptimizeWarning):
  1625. super().test_bug_8174()
  1626. class TestLinprogSimplexNoPresolve(LinprogSimplexTests):
  1627. def setup_method(self):
  1628. self.options = {'presolve': False}
  1629. is_32_bit = np.intp(0).itemsize < 8
  1630. is_linux = sys.platform.startswith('linux')
  1631. @pytest.mark.xfail(
  1632. condition=is_32_bit and is_linux,
  1633. reason='Fails with warning on 32-bit linux')
  1634. def test_bug_5400(self):
  1635. super().test_bug_5400()
  1636. def test_bug_6139_low_tol(self):
  1637. # Linprog(method='simplex') fails to find a basic feasible solution
  1638. # if phase 1 pseudo-objective function is outside the provided tol.
  1639. # https://github.com/scipy/scipy/issues/6139
  1640. # Without ``presolve`` eliminating such rows the result is incorrect.
  1641. self.options.update({'tol': 1e-12})
  1642. with pytest.raises(AssertionError, match='linprog status 4'):
  1643. return super().test_bug_6139()
  1644. def test_bug_7237_low_tol(self):
  1645. pytest.skip("Simplex fails on this problem.")
  1646. def test_bug_8174_low_tol(self):
  1647. # Fails if the tolerance is too strict. Here, we test that
  1648. # even if the solution is wrong, the appropriate warning is issued.
  1649. self.options.update({'tol': 1e-12})
  1650. with pytest.warns(OptimizeWarning):
  1651. super().test_bug_8174()
  1652. def test_unbounded_no_nontrivial_constraints_1(self):
  1653. pytest.skip("Tests behavior specific to presolve")
  1654. def test_unbounded_no_nontrivial_constraints_2(self):
  1655. pytest.skip("Tests behavior specific to presolve")
  1656. #######################################
  1657. # Interior-Point Option-Specific Tests#
  1658. #######################################
  1659. class TestLinprogIPDense(LinprogIPTests):
  1660. options = {"sparse": False}
  1661. if has_cholmod:
  1662. class TestLinprogIPSparseCholmod(LinprogIPTests):
  1663. options = {"sparse": True, "cholesky": True}
  1664. if has_umfpack:
  1665. class TestLinprogIPSparseUmfpack(LinprogIPTests):
  1666. options = {"sparse": True, "cholesky": False}
  1667. def test_network_flow_limited_capacity(self):
  1668. pytest.skip("Failing due to numerical issues on some platforms.")
  1669. class TestLinprogIPSparse(LinprogIPTests):
  1670. options = {"sparse": True, "cholesky": False, "sym_pos": False}
  1671. @pytest.mark.xfail_on_32bit("This test is sensitive to machine epsilon level "
  1672. "perturbations in linear system solution in "
  1673. "_linprog_ip._sym_solve.")
  1674. def test_bug_6139(self):
  1675. super().test_bug_6139()
  1676. @pytest.mark.xfail(reason='Fails with ATLAS, see gh-7877')
  1677. def test_bug_6690(self):
  1678. # Test defined in base class, but can't mark as xfail there
  1679. super().test_bug_6690()
  1680. def test_magic_square_sparse_no_presolve(self):
  1681. # test linprog with a problem with a rank-deficient A_eq matrix
  1682. A_eq, b_eq, c, _, _ = magic_square(3)
  1683. bounds = (0, 1)
  1684. with suppress_warnings() as sup:
  1685. if has_umfpack:
  1686. sup.filter(UmfpackWarning)
  1687. sup.filter(MatrixRankWarning, "Matrix is exactly singular")
  1688. sup.filter(OptimizeWarning, "Solving system with option...")
  1689. o = {key: self.options[key] for key in self.options}
  1690. o["presolve"] = False
  1691. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1692. method=self.method, options=o)
  1693. _assert_success(res, desired_fun=1.730550597)
  1694. def test_sparse_solve_options(self):
  1695. # checking that problem is solved with all column permutation options
  1696. A_eq, b_eq, c, _, _ = magic_square(3)
  1697. with suppress_warnings() as sup:
  1698. sup.filter(OptimizeWarning, "A_eq does not appear...")
  1699. sup.filter(OptimizeWarning, "Invalid permc_spec option")
  1700. o = {key: self.options[key] for key in self.options}
  1701. permc_specs = ('NATURAL', 'MMD_ATA', 'MMD_AT_PLUS_A',
  1702. 'COLAMD', 'ekki-ekki-ekki')
  1703. # 'ekki-ekki-ekki' raises warning about invalid permc_spec option
  1704. # and uses default
  1705. for permc_spec in permc_specs:
  1706. o["permc_spec"] = permc_spec
  1707. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1708. method=self.method, options=o)
  1709. _assert_success(res, desired_fun=1.730550597)
  1710. class TestLinprogIPSparsePresolve(LinprogIPTests):
  1711. options = {"sparse": True, "_sparse_presolve": True}
  1712. @pytest.mark.xfail_on_32bit("This test is sensitive to machine epsilon level "
  1713. "perturbations in linear system solution in "
  1714. "_linprog_ip._sym_solve.")
  1715. def test_bug_6139(self):
  1716. super().test_bug_6139()
  1717. def test_enzo_example_c_with_infeasibility(self):
  1718. pytest.skip('_sparse_presolve=True incompatible with presolve=False')
  1719. @pytest.mark.xfail(reason='Fails with ATLAS, see gh-7877')
  1720. def test_bug_6690(self):
  1721. # Test defined in base class, but can't mark as xfail there
  1722. super().test_bug_6690()
  1723. @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  1724. class TestLinprogIPSpecific:
  1725. method = "interior-point"
  1726. # the following tests don't need to be performed separately for
  1727. # sparse presolve, sparse after presolve, and dense
  1728. def test_solver_select(self):
  1729. # check that default solver is selected as expected
  1730. if has_cholmod:
  1731. options = {'sparse': True, 'cholesky': True}
  1732. elif has_umfpack:
  1733. options = {'sparse': True, 'cholesky': False}
  1734. else:
  1735. options = {'sparse': True, 'cholesky': False, 'sym_pos': False}
  1736. A, b, c = lpgen_2d(20, 20)
  1737. res1 = linprog(c, A_ub=A, b_ub=b, method=self.method, options=options)
  1738. res2 = linprog(c, A_ub=A, b_ub=b, method=self.method) # default solver
  1739. assert_allclose(res1.fun, res2.fun,
  1740. err_msg="linprog default solver unexpected result",
  1741. rtol=2e-15, atol=1e-15)
  1742. def test_unbounded_below_no_presolve_original(self):
  1743. # formerly caused segfault in TravisCI w/ "cholesky":True
  1744. c = [-1]
  1745. bounds = [(None, 1)]
  1746. res = linprog(c=c, bounds=bounds,
  1747. method=self.method,
  1748. options={"presolve": False, "cholesky": True})
  1749. _assert_success(res, desired_fun=-1)
  1750. def test_cholesky(self):
  1751. # use cholesky factorization and triangular solves
  1752. A, b, c = lpgen_2d(20, 20)
  1753. res = linprog(c, A_ub=A, b_ub=b, method=self.method,
  1754. options={"cholesky": True}) # only for dense
  1755. _assert_success(res, desired_fun=-64.049494229)
  1756. def test_alternate_initial_point(self):
  1757. # use "improved" initial point
  1758. A, b, c = lpgen_2d(20, 20)
  1759. with suppress_warnings() as sup:
  1760. sup.filter(RuntimeWarning, "scipy.linalg.solve\nIll...")
  1761. sup.filter(OptimizeWarning, "Solving system with option...")
  1762. sup.filter(LinAlgWarning, "Ill-conditioned matrix...")
  1763. res = linprog(c, A_ub=A, b_ub=b, method=self.method,
  1764. options={"ip": True, "disp": True})
  1765. # ip code is independent of sparse/dense
  1766. _assert_success(res, desired_fun=-64.049494229)
  1767. def test_bug_8664(self):
  1768. # interior-point has trouble with this when presolve is off
  1769. c = [4]
  1770. A_ub = [[2], [5]]
  1771. b_ub = [4, 4]
  1772. A_eq = [[0], [-8], [9]]
  1773. b_eq = [3, 2, 10]
  1774. with suppress_warnings() as sup:
  1775. sup.filter(RuntimeWarning)
  1776. sup.filter(OptimizeWarning, "Solving system with option...")
  1777. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1778. method=self.method, options={"presolve": False})
  1779. assert_(not res.success, "Incorrectly reported success")
  1780. ########################################
  1781. # Revised Simplex Option-Specific Tests#
  1782. ########################################
  1783. class TestLinprogRSCommon(LinprogRSTests):
  1784. options = {}
  1785. def test_cyclic_bland(self):
  1786. pytest.skip("Intermittent failure acceptable.")
  1787. def test_nontrivial_problem_with_guess(self):
  1788. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  1789. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1790. method=self.method, options=self.options, x0=x_star)
  1791. _assert_success(res, desired_fun=f_star, desired_x=x_star)
  1792. assert_equal(res.nit, 0)
  1793. def test_nontrivial_problem_with_unbounded_variables(self):
  1794. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  1795. bounds = [(None, None), (None, None), (0, None), (None, None)]
  1796. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1797. method=self.method, options=self.options, x0=x_star)
  1798. _assert_success(res, desired_fun=f_star, desired_x=x_star)
  1799. assert_equal(res.nit, 0)
  1800. def test_nontrivial_problem_with_bounded_variables(self):
  1801. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  1802. bounds = [(None, 1), (1, None), (0, None), (.4, .6)]
  1803. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1804. method=self.method, options=self.options, x0=x_star)
  1805. _assert_success(res, desired_fun=f_star, desired_x=x_star)
  1806. assert_equal(res.nit, 0)
  1807. def test_nontrivial_problem_with_negative_unbounded_variable(self):
  1808. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  1809. b_eq = [4]
  1810. x_star = np.array([-219/385, 582/385, 0, 4/10])
  1811. f_star = 3951/385
  1812. bounds = [(None, None), (1, None), (0, None), (.4, .6)]
  1813. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1814. method=self.method, options=self.options, x0=x_star)
  1815. _assert_success(res, desired_fun=f_star, desired_x=x_star)
  1816. assert_equal(res.nit, 0)
  1817. def test_nontrivial_problem_with_bad_guess(self):
  1818. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  1819. bad_guess = [1, 2, 3, .5]
  1820. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  1821. method=self.method, options=self.options, x0=bad_guess)
  1822. assert_equal(res.status, 6)
  1823. def test_redundant_constraints_with_guess(self):
  1824. A, b, c, _, _ = magic_square(3)
  1825. p = np.random.rand(*c.shape)
  1826. with suppress_warnings() as sup:
  1827. sup.filter(OptimizeWarning, "A_eq does not appear...")
  1828. sup.filter(RuntimeWarning, "invalid value encountered")
  1829. sup.filter(LinAlgWarning)
  1830. res = linprog(c, A_eq=A, b_eq=b, method=self.method)
  1831. res2 = linprog(c, A_eq=A, b_eq=b, method=self.method, x0=res.x)
  1832. res3 = linprog(c + p, A_eq=A, b_eq=b, method=self.method, x0=res.x)
  1833. _assert_success(res2, desired_fun=1.730550597)
  1834. assert_equal(res2.nit, 0)
  1835. _assert_success(res3)
  1836. assert_(res3.nit < res.nit) # hot start reduces iterations
  1837. class TestLinprogRSBland(LinprogRSTests):
  1838. options = {"pivot": "bland"}
  1839. ############################################
  1840. # HiGHS-Simplex-Dual Option-Specific Tests #
  1841. ############################################
  1842. class TestLinprogHiGHSSimplexDual(LinprogHiGHSTests):
  1843. method = "highs-ds"
  1844. options = {}
  1845. def test_lad_regression(self):
  1846. '''
  1847. The scaled model should be optimal, i.e. not produce unscaled model
  1848. infeasible. See https://github.com/ERGO-Code/HiGHS/issues/494.
  1849. '''
  1850. # Test to ensure gh-13610 is resolved (mismatch between HiGHS scaled
  1851. # and unscaled model statuses)
  1852. c, A_ub, b_ub, bnds = l1_regression_prob()
  1853. res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bnds,
  1854. method=self.method, options=self.options)
  1855. assert_equal(res.status, 0)
  1856. assert_(res.x is not None)
  1857. assert_(np.all(res.slack > -1e-6))
  1858. assert_(np.all(res.x <= [np.inf if ub is None else ub
  1859. for lb, ub in bnds]))
  1860. assert_(np.all(res.x >= [-np.inf if lb is None else lb - 1e-7
  1861. for lb, ub in bnds]))
  1862. ###################################
  1863. # HiGHS-IPM Option-Specific Tests #
  1864. ###################################
  1865. class TestLinprogHiGHSIPM(LinprogHiGHSTests):
  1866. method = "highs-ipm"
  1867. options = {}
  1868. ###################################
  1869. # HiGHS-MIP Option-Specific Tests #
  1870. ###################################
  1871. class TestLinprogHiGHSMIP():
  1872. method = "highs"
  1873. options = {}
  1874. @pytest.mark.xfail(condition=(sys.maxsize < 2 ** 32 and
  1875. platform.system() == "Linux"),
  1876. run=False,
  1877. reason="gh-16347")
  1878. def test_mip1(self):
  1879. # solve non-relaxed magic square problem (finally!)
  1880. # also check that values are all integers - they don't always
  1881. # come out of HiGHS that way
  1882. n = 4
  1883. A, b, c, numbers, M = magic_square(n)
  1884. bounds = [(0, 1)] * len(c)
  1885. integrality = [1] * len(c)
  1886. res = linprog(c=c*0, A_eq=A, b_eq=b, bounds=bounds,
  1887. method=self.method, integrality=integrality)
  1888. s = (numbers.flatten() * res.x).reshape(n**2, n, n)
  1889. square = np.sum(s, axis=0)
  1890. np.testing.assert_allclose(square.sum(axis=0), M)
  1891. np.testing.assert_allclose(square.sum(axis=1), M)
  1892. np.testing.assert_allclose(np.diag(square).sum(), M)
  1893. np.testing.assert_allclose(np.diag(square[:, ::-1]).sum(), M)
  1894. np.testing.assert_allclose(res.x, np.round(res.x), atol=1e-12)
  1895. def test_mip2(self):
  1896. # solve MIP with inequality constraints and all integer constraints
  1897. # source: slide 5,
  1898. # https://www.cs.upc.edu/~erodri/webpage/cps/theory/lp/milp/slides.pdf
  1899. # use all array inputs to test gh-16681 (integrality couldn't be array)
  1900. A_ub = np.array([[2, -2], [-8, 10]])
  1901. b_ub = np.array([-1, 13])
  1902. c = -np.array([1, 1])
  1903. bounds = np.array([(0, np.inf)] * len(c))
  1904. integrality = np.ones_like(c)
  1905. res = linprog(c=c, A_ub=A_ub, b_ub=b_ub, bounds=bounds,
  1906. method=self.method, integrality=integrality)
  1907. np.testing.assert_allclose(res.x, [1, 2])
  1908. np.testing.assert_allclose(res.fun, -3)
  1909. def test_mip3(self):
  1910. # solve MIP with inequality constraints and all integer constraints
  1911. # source: https://en.wikipedia.org/wiki/Integer_programming#Example
  1912. A_ub = np.array([[-1, 1], [3, 2], [2, 3]])
  1913. b_ub = np.array([1, 12, 12])
  1914. c = -np.array([0, 1])
  1915. bounds = [(0, np.inf)] * len(c)
  1916. integrality = [1] * len(c)
  1917. res = linprog(c=c, A_ub=A_ub, b_ub=b_ub, bounds=bounds,
  1918. method=self.method, integrality=integrality)
  1919. np.testing.assert_allclose(res.fun, -2)
  1920. # two optimal solutions possible, just need one of them
  1921. assert np.allclose(res.x, [1, 2]) or np.allclose(res.x, [2, 2])
  1922. def test_mip4(self):
  1923. # solve MIP with inequality constraints and only one integer constraint
  1924. # source: https://www.mathworks.com/help/optim/ug/intlinprog.html
  1925. A_ub = np.array([[-1, -2], [-4, -1], [2, 1]])
  1926. b_ub = np.array([14, -33, 20])
  1927. c = np.array([8, 1])
  1928. bounds = [(0, np.inf)] * len(c)
  1929. integrality = [0, 1]
  1930. res = linprog(c=c, A_ub=A_ub, b_ub=b_ub, bounds=bounds,
  1931. method=self.method, integrality=integrality)
  1932. np.testing.assert_allclose(res.x, [6.5, 7])
  1933. np.testing.assert_allclose(res.fun, 59)
  1934. def test_mip5(self):
  1935. # solve MIP with inequality and inequality constraints
  1936. # source: https://www.mathworks.com/help/optim/ug/intlinprog.html
  1937. A_ub = np.array([[1, 1, 1]])
  1938. b_ub = np.array([7])
  1939. A_eq = np.array([[4, 2, 1]])
  1940. b_eq = np.array([12])
  1941. c = np.array([-3, -2, -1])
  1942. bounds = [(0, np.inf), (0, np.inf), (0, 1)]
  1943. integrality = [0, 1, 0]
  1944. res = linprog(c=c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
  1945. bounds=bounds, method=self.method,
  1946. integrality=integrality)
  1947. np.testing.assert_allclose(res.x, [0, 6, 0])
  1948. np.testing.assert_allclose(res.fun, -12)
  1949. # gh-16897: these fields were not present, ensure that they are now
  1950. assert res.get("mip_node_count", None) is not None
  1951. assert res.get("mip_dual_bound", None) is not None
  1952. assert res.get("mip_gap", None) is not None
  1953. @pytest.mark.slow
  1954. @pytest.mark.timeout(120) # prerelease_deps_coverage_64bit_blas job
  1955. def test_mip6(self):
  1956. # solve a larger MIP with only equality constraints
  1957. # source: https://www.mathworks.com/help/optim/ug/intlinprog.html
  1958. A_eq = np.array([[22, 13, 26, 33, 21, 3, 14, 26],
  1959. [39, 16, 22, 28, 26, 30, 23, 24],
  1960. [18, 14, 29, 27, 30, 38, 26, 26],
  1961. [41, 26, 28, 36, 18, 38, 16, 26]])
  1962. b_eq = np.array([7872, 10466, 11322, 12058])
  1963. c = np.array([2, 10, 13, 17, 7, 5, 7, 3])
  1964. bounds = [(0, np.inf)]*8
  1965. integrality = [1]*8
  1966. res = linprog(c=c, A_eq=A_eq, b_eq=b_eq, bounds=bounds,
  1967. method=self.method, integrality=integrality)
  1968. np.testing.assert_allclose(res.fun, 1854)
  1969. @pytest.mark.xslow
  1970. def test_mip_rel_gap_passdown(self):
  1971. # MIP taken from test_mip6, solved with different values of mip_rel_gap
  1972. # solve a larger MIP with only equality constraints
  1973. # source: https://www.mathworks.com/help/optim/ug/intlinprog.html
  1974. A_eq = np.array([[22, 13, 26, 33, 21, 3, 14, 26],
  1975. [39, 16, 22, 28, 26, 30, 23, 24],
  1976. [18, 14, 29, 27, 30, 38, 26, 26],
  1977. [41, 26, 28, 36, 18, 38, 16, 26]])
  1978. b_eq = np.array([7872, 10466, 11322, 12058])
  1979. c = np.array([2, 10, 13, 17, 7, 5, 7, 3])
  1980. bounds = [(0, np.inf)]*8
  1981. integrality = [1]*8
  1982. mip_rel_gaps = [0.5, 0.25, 0.01, 0.001]
  1983. sol_mip_gaps = []
  1984. for mip_rel_gap in mip_rel_gaps:
  1985. res = linprog(c=c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq,
  1986. bounds=bounds, method=self.method,
  1987. integrality=integrality,
  1988. options={"mip_rel_gap": mip_rel_gap})
  1989. final_mip_gap = res["mip_gap"]
  1990. # assert that the solution actually has mip_gap lower than the
  1991. # required mip_rel_gap supplied
  1992. assert final_mip_gap <= mip_rel_gap
  1993. sol_mip_gaps.append(final_mip_gap)
  1994. # make sure that the mip_rel_gap parameter is actually doing something
  1995. # check that differences between solution gaps are declining
  1996. # monotonically with the mip_rel_gap parameter. np.diff does
  1997. # x[i+1] - x[i], so flip the array before differencing to get
  1998. # what should be a positive, monotone decreasing series of solution
  1999. # gaps
  2000. gap_diffs = np.diff(np.flip(sol_mip_gaps))
  2001. assert np.all(gap_diffs >= 0)
  2002. assert not np.all(gap_diffs == 0)
  2003. ###########################
  2004. # Autoscale-Specific Tests#
  2005. ###########################
  2006. @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  2007. class AutoscaleTests:
  2008. options = {"autoscale": True}
  2009. test_bug_6139 = LinprogCommonTests.test_bug_6139
  2010. test_bug_6690 = LinprogCommonTests.test_bug_6690
  2011. test_bug_7237 = LinprogCommonTests.test_bug_7237
  2012. class TestAutoscaleIP(AutoscaleTests):
  2013. method = "interior-point"
  2014. def test_bug_6139(self):
  2015. self.options['tol'] = 1e-10
  2016. return AutoscaleTests.test_bug_6139(self)
  2017. class TestAutoscaleSimplex(AutoscaleTests):
  2018. method = "simplex"
  2019. class TestAutoscaleRS(AutoscaleTests):
  2020. method = "revised simplex"
  2021. def test_nontrivial_problem_with_guess(self):
  2022. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  2023. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  2024. method=self.method, options=self.options, x0=x_star)
  2025. _assert_success(res, desired_fun=f_star, desired_x=x_star)
  2026. assert_equal(res.nit, 0)
  2027. def test_nontrivial_problem_with_bad_guess(self):
  2028. c, A_ub, b_ub, A_eq, b_eq, x_star, f_star = nontrivial_problem()
  2029. bad_guess = [1, 2, 3, .5]
  2030. res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
  2031. method=self.method, options=self.options, x0=bad_guess)
  2032. assert_equal(res.status, 6)
  2033. ###########################
  2034. # Redundancy Removal Tests#
  2035. ###########################
  2036. @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  2037. class RRTests:
  2038. method = "interior-point"
  2039. LCT = LinprogCommonTests
  2040. # these are a few of the existing tests that have redundancy
  2041. test_RR_infeasibility = LCT.test_remove_redundancy_infeasibility
  2042. test_bug_10349 = LCT.test_bug_10349
  2043. test_bug_7044 = LCT.test_bug_7044
  2044. test_NFLC = LCT.test_network_flow_limited_capacity
  2045. test_enzo_example_b = LCT.test_enzo_example_b
  2046. class TestRRSVD(RRTests):
  2047. options = {"rr_method": "SVD"}
  2048. class TestRRPivot(RRTests):
  2049. options = {"rr_method": "pivot"}
  2050. class TestRRID(RRTests):
  2051. options = {"rr_method": "ID"}