test_simplex.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. from sympy.core.numbers import Rational
  2. from sympy.core.relational import Eq, Ne
  3. from sympy.core.symbol import symbols
  4. from sympy.core.sympify import sympify
  5. from sympy.core.singleton import S
  6. from sympy.core.random import random, choice
  7. from sympy.functions.elementary.miscellaneous import sqrt
  8. from sympy.ntheory.generate import randprime
  9. from sympy.matrices.dense import Matrix
  10. from sympy.solvers.solveset import linear_eq_to_matrix
  11. from sympy.solvers.simplex import (_lp as lp, _primal_dual,
  12. UnboundedLPError, InfeasibleLPError, lpmin, lpmax,
  13. _m, _abcd, _simplex, linprog)
  14. from sympy.external.importtools import import_module
  15. from sympy.testing.pytest import raises
  16. from sympy.abc import x, y, z
  17. np = import_module("numpy")
  18. scipy = import_module("scipy")
  19. def test_lp():
  20. r1 = y + 2*z <= 3
  21. r2 = -x - 3*z <= -2
  22. r3 = 2*x + y + 7*z <= 5
  23. constraints = [r1, r2, r3, x >= 0, y >= 0, z >= 0]
  24. objective = -x - y - 5 * z
  25. ans = optimum, argmax = lp(max, objective, constraints)
  26. assert ans == lpmax(objective, constraints)
  27. assert objective.subs(argmax) == optimum
  28. for constr in constraints:
  29. assert constr.subs(argmax) == True
  30. r1 = x - y + 2*z <= 3
  31. r2 = -x + 2*y - 3*z <= -2
  32. r3 = 2*x + y - 7*z <= -5
  33. constraints = [r1, r2, r3, x >= 0, y >= 0, z >= 0]
  34. objective = -x - y - 5*z
  35. ans = optimum, argmax = lp(max, objective, constraints)
  36. assert ans == lpmax(objective, constraints)
  37. assert objective.subs(argmax) == optimum
  38. for constr in constraints:
  39. assert constr.subs(argmax) == True
  40. r1 = x - y + 2*z <= -4
  41. r2 = -x + 2*y - 3*z <= 8
  42. r3 = 2*x + y - 7*z <= 10
  43. constraints = [r1, r2, r3, x >= 0, y >= 0, z >= 0]
  44. const = 2
  45. objective = -x-y-5*z+const # has constant term
  46. ans = optimum, argmax = lp(max, objective, constraints)
  47. assert ans == lpmax(objective, constraints)
  48. assert objective.subs(argmax) == optimum
  49. for constr in constraints:
  50. assert constr.subs(argmax) == True
  51. # Section 4 Problem 1 from
  52. # http://web.tecnico.ulisboa.pt/mcasquilho/acad/or/ftp/FergusonUCLA_LP.pdf
  53. # answer on page 55
  54. v = x1, x2, x3, x4 = symbols('x1 x2 x3 x4')
  55. r1 = x1 - x2 - 2*x3 - x4 <= 4
  56. r2 = 2*x1 + x3 -4*x4 <= 2
  57. r3 = -2*x1 + x2 + x4 <= 1
  58. objective, constraints = x1 - 2*x2 - 3*x3 - x4, [r1, r2, r3] + [
  59. i >= 0 for i in v]
  60. ans = optimum, argmax = lp(max, objective, constraints)
  61. assert ans == lpmax(objective, constraints)
  62. assert ans == (4, {x1: 7, x2: 0, x3: 0, x4: 3})
  63. # input contains Floats
  64. r1 = x - y + 2.0*z <= -4
  65. r2 = -x + 2*y - 3.0*z <= 8
  66. r3 = 2*x + y - 7*z <= 10
  67. constraints = [r1, r2, r3] + [i >= 0 for i in (x, y, z)]
  68. objective = -x-y-5*z
  69. optimum, argmax = lp(max, objective, constraints)
  70. assert objective.subs(argmax) == optimum
  71. for constr in constraints:
  72. assert constr.subs(argmax) == True
  73. # input contains non-float or non-Rational
  74. r1 = x - y + sqrt(2) * z <= -4
  75. r2 = -x + 2*y - 3*z <= 8
  76. r3 = 2*x + y - 7*z <= 10
  77. raises(TypeError, lambda: lp(max, -x-y-5*z, [r1, r2, r3]))
  78. r1 = x >= 0
  79. raises(UnboundedLPError, lambda: lp(max, x, [r1]))
  80. r2 = x <= -1
  81. raises(InfeasibleLPError, lambda: lp(max, x, [r1, r2]))
  82. # strict inequalities are not allowed
  83. r1 = x > 0
  84. raises(TypeError, lambda: lp(max, x, [r1]))
  85. # not equals not allowed
  86. r1 = Ne(x, 0)
  87. raises(TypeError, lambda: lp(max, x, [r1]))
  88. def make_random_problem(nvar=2, num_constraints=2, sparsity=.1):
  89. def rand():
  90. if random() < sparsity:
  91. return sympify(0)
  92. int1, int2 = [randprime(0, 200) for _ in range(2)]
  93. return Rational(int1, int2)*choice([-1, 1])
  94. variables = symbols('x1:%s' % (nvar + 1))
  95. constraints = [(sum(rand()*x for x in variables) <= rand())
  96. for _ in range(num_constraints)]
  97. objective = sum(rand() * x for x in variables)
  98. return objective, constraints, variables
  99. # equality
  100. r1 = Eq(x, y)
  101. r2 = Eq(y, z)
  102. r3 = z <= 3
  103. constraints = [r1, r2, r3]
  104. objective = x
  105. ans = optimum, argmax = lp(max, objective, constraints)
  106. assert ans == lpmax(objective, constraints)
  107. assert objective.subs(argmax) == optimum
  108. for constr in constraints:
  109. assert constr.subs(argmax) == True
  110. def test_simplex():
  111. L = [
  112. [[1, 1], [-1, 1], [0, 1], [-1, 0]],
  113. [5, 1, 2, -1],
  114. [[1, 1]],
  115. [-1]]
  116. A, B, C, D = _abcd(_m(*L), list=False)
  117. assert _simplex(A, B, -C, -D) == (-6, [3, 2], [1, 0, 0, 0])
  118. assert _simplex(A, B, -C, -D, dual=True) == (-6,
  119. [1, 0, 0, 0], [5, 0])
  120. assert _simplex([[]],[],[[1]],[0]) == (0, [0], [])
  121. # handling of Eq (or Eq-like x<=y, x>=y conditions)
  122. assert lpmax(x - y, [x <= y + 2, x >= y + 2, x >= 0, y >= 0]
  123. ) == (2, {x: 2, y: 0})
  124. assert lpmax(x - y, [x <= y + 2, Eq(x, y + 2), x >= 0, y >= 0]
  125. ) == (2, {x: 2, y: 0})
  126. assert lpmax(x - y, [x <= y + 2, Eq(x, 2)]) == (2, {x: 2, y: 0})
  127. assert lpmax(y, [Eq(y, 2)]) == (2, {y: 2})
  128. # the conditions are equivalent to Eq(x, y + 2)
  129. assert lpmin(y, [x <= y + 2, x >= y + 2, y >= 0]
  130. ) == (0, {x: 2, y: 0})
  131. # equivalent to Eq(y, -2)
  132. assert lpmax(y, [0 <= y + 2, 0 >= y + 2]) == (-2, {y: -2})
  133. assert lpmax(y, [0 <= y + 2, 0 >= y + 2, y <= 0]
  134. ) == (-2, {y: -2})
  135. # extra symbols symbols
  136. assert lpmin(x, [y >= 1, x >= y]) == (1, {x: 1, y: 1})
  137. assert lpmin(x, [y >= 1, x >= y + z, x >= 0, z >= 0]
  138. ) == (1, {x: 1, y: 1, z: 0})
  139. # detect oscillation
  140. # o1
  141. v = x1, x2, x3, x4 = symbols('x1 x2 x3 x4')
  142. raises(InfeasibleLPError, lambda: lpmin(
  143. 9*x2 - 8*x3 + 3*x4 + 6,
  144. [5*x2 - 2*x3 <= 0,
  145. -x1 - 8*x2 + 9*x3 <= -3,
  146. 10*x1 - x2+ 9*x4 <= -4] + [i >= 0 for i in v]))
  147. # o2 - equations fed to lpmin are changed into a matrix
  148. # system that doesn't oscillate and has the same solution
  149. # as below
  150. M = linear_eq_to_matrix
  151. f = 5*x2 + x3 + 4*x4 - x1
  152. L = 5*x2 + 2*x3 + 5*x4 - (x1 + 5)
  153. cond = [L <= 0] + [Eq(3*x2 + x4, 2), Eq(-x1 + x3 + 2*x4, 1)]
  154. c, d = M(f, v)
  155. a, b = M(L, v)
  156. aeq, beq = M(cond[1:], v)
  157. ans = (S(9)/2, [0, S(1)/2, 0, S(1)/2])
  158. assert linprog(c, a, b, aeq, beq, bounds=(0, 1)) == ans
  159. lpans = lpmin(f, cond + [x1 >= 0, x1 <= 1,
  160. x2 >= 0, x2 <= 1, x3 >= 0, x3 <= 1, x4 >= 0, x4 <= 1])
  161. assert (lpans[0], list(lpans[1].values())) == ans
  162. def test_lpmin_lpmax():
  163. v = x1, x2, y1, y2 = symbols('x1 x2 y1 y2')
  164. L = [[1, -1]], [1], [[1, 1]], [2]
  165. a, b, c, d = [Matrix(i) for i in L]
  166. m = Matrix([[a, b], [c, d]])
  167. f, constr = _primal_dual(m)[0]
  168. ans = lpmin(f, constr + [i >= 0 for i in v[:2]])
  169. assert ans == (-1, {x1: 1, x2: 0}),ans
  170. L = [[1, -1], [1, 1]], [1, 1], [[1, 1]], [2]
  171. a, b, c, d = [Matrix(i) for i in L]
  172. m = Matrix([[a, b], [c, d]])
  173. f, constr = _primal_dual(m)[1]
  174. ans = lpmax(f, constr + [i >= 0 for i in v[-2:]])
  175. assert ans == (-1, {y1: 1, y2: 0})
  176. def test_linprog():
  177. for do in range(2):
  178. if not do:
  179. M = lambda a, b: linear_eq_to_matrix(a, b)
  180. else:
  181. # check matrices as list
  182. M = lambda a, b: tuple([
  183. i.tolist() for i in linear_eq_to_matrix(a, b)])
  184. v = x, y, z = symbols('x1:4')
  185. f = x + y - 2*z
  186. c = M(f, v)[0]
  187. ineq = [7*x + 4*y - 7*z <= 3,
  188. 3*x - y + 10*z <= 6,
  189. x >= 0, y >= 0, z >= 0]
  190. ab = M([i.lts - i.gts for i in ineq], v)
  191. ans = (-S(6)/5, [0, 0, S(3)/5])
  192. assert lpmin(f, ineq) == (ans[0], dict(zip(v, ans[1])))
  193. assert linprog(c, *ab) == ans
  194. f += 1
  195. c = M(f, v)[0]
  196. eq = [Eq(y - 9*x, 1)]
  197. abeq = M([i.lhs - i.rhs for i in eq], v)
  198. ans = (1 - S(2)/5, [0, 1, S(7)/10])
  199. assert lpmin(f, ineq + eq) == (ans[0], dict(zip(v, ans[1])))
  200. assert linprog(c, *ab, *abeq) == (ans[0] - 1, ans[1])
  201. eq = [z - y <= S.Half]
  202. abeq = M([i.lhs - i.rhs for i in eq], v)
  203. ans = (1 - S(10)/9, [0, S(1)/9, S(11)/18])
  204. assert lpmin(f, ineq + eq) == (ans[0], dict(zip(v, ans[1])))
  205. assert linprog(c, *ab, *abeq) == (ans[0] - 1, ans[1])
  206. bounds = [(0, None), (0, None), (None, S.Half)]
  207. ans = (0, [0, 0, S.Half])
  208. assert lpmin(f, ineq + [z <= S.Half]) == (
  209. ans[0], dict(zip(v, ans[1])))
  210. assert linprog(c, *ab, bounds=bounds) == (ans[0] - 1, ans[1])
  211. assert linprog(c, *ab, bounds={v.index(z): bounds[-1]}
  212. ) == (ans[0] - 1, ans[1])
  213. eq = [z - y <= S.Half]
  214. assert linprog([[1]], [], [], bounds=(2, 3)) == (2, [2])
  215. assert linprog([1], [], [], bounds=(2, 3)) == (2, [2])
  216. assert linprog([1], bounds=(2, 3)) == (2, [2])
  217. assert linprog([1, -1], [[1, 1]], [2], bounds={1:(None, None)}
  218. ) == (-2, [0, 2])
  219. assert linprog([1, -1], [[1, 1]], [5], bounds={1:(3, None)}
  220. ) == (-5, [0, 5])