test_linprog.py 103 KB

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