Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Tests for GP and SP classes"""
2import unittest
3import sys
4import numpy as np
5from gpkit import (Model, settings, VectorVariable, Variable,
6 SignomialsEnabled, ArrayVariable, SignomialEquality)
7from gpkit.constraints.bounded import Bounded
8from gpkit.small_classes import CootMatrix
9from gpkit import NamedVariables, units, parse_variables
10from gpkit.constraints.relax import ConstraintsRelaxed
11from gpkit.constraints.relax import ConstraintsRelaxedEqually
12from gpkit.constraints.relax import ConstantsRelaxed
13from gpkit.exceptions import (UnknownInfeasible, InvalidPosynomial,
14 InvalidGPConstraint, UnnecessarySGP,
15 PrimalInfeasible, DualInfeasible, UnboundedGP)
18NDIGS = {"cvxopt": 5, "mosek_cli": 5, "mosek_conif": 3}
19# name: decimal places of accuracy achieved in these tests
21# pylint: disable=invalid-name,attribute-defined-outside-init,unused-variable,undefined-variable,exec-used
24class TestGP(unittest.TestCase):
25 """
26 Test GeometricPrograms.
27 This TestCase gets run once for each installed solver.
28 """
29 name = "TestGP_"
30 # solver and ndig get set in loop at bottom this file, a bit hacky
31 solver = None
32 ndig = None
34 def test_no_monomial_constraints(self):
35 x = Variable("x")
36 sol = Model(x, [x + 1/x <= 3]).solve(solver=self.solver, verbosity=0)
37 self.assertAlmostEqual(sol["cost"], 0.381966, self.ndig)
39 def test_trivial_gp(self):
40 """
41 Create and solve a trivial GP:
42 minimize x + 2y
43 subject to xy >= 1
45 The global optimum is (x, y) = (sqrt(2), 1/sqrt(2)).
46 """
47 x = Variable("x")
48 y = Variable("y")
49 prob = Model(cost=(x + 2*y),
50 constraints=[x*y >= 1])
51 sol = prob.solve(solver=self.solver, verbosity=0)
52 self.assertEqual(type(prob.latex()), str)
53 # pylint: disable=protected-access
54 self.assertEqual(type(prob._repr_latex_()), str)
55 self.assertAlmostEqual(sol("x"), np.sqrt(2.), self.ndig)
56 self.assertAlmostEqual(sol("y"), 1/np.sqrt(2.), self.ndig)
57 self.assertAlmostEqual(sol("x") + 2*sol("y"),
58 2*np.sqrt(2),
59 self.ndig)
60 self.assertAlmostEqual(sol["cost"], 2*np.sqrt(2), self.ndig)
62 def test_dup_eq_constraint(self):
63 # from https://github.com/convexengineering/gpkit/issues/1551
64 a = Variable("a", 1)
65 b = Variable("b")
66 c = Variable("c", 2)
67 d = Variable("d")
68 z = Variable("z", 0.5)
70 # create a simple GP with equality constraints
71 const = [
72 z == b/a,
73 z == d/c,
74 ]
76 # simple cost
77 cost = a + b + c + d
79 # create a model
80 m = Model(cost, const)
82 # solve the first version of the model (solves successfully)
83 m.solve(verbosity=0, solver=self.solver)
85 # add a redundant equality constraint
86 m.extend([
87 z == b/a
88 ])
90 # solver will fail and attempt to debug
91 m.solve(verbosity=0, solver=self.solver)
94 def test_sigeq(self):
95 x = Variable("x")
96 y = VectorVariable(1, "y")
97 c = Variable("c")
98 # test left vector input to sigeq
99 with SignomialsEnabled():
100 m = Model(c, [c >= (x + 0.25)**2 + (y - 0.5)**2,
101 SignomialEquality(x**2 + x, y)])
102 sol = m.localsolve(solver=self.solver, verbosity=0)
103 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig)
104 self.assertAlmostEqual(sol("y")[0], 0.1908254, self.ndig)
105 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig)
106 # test right vector input to sigeq
107 with SignomialsEnabled():
108 m = Model(c, [c >= (x + 0.25)**2 + (y - 0.5)**2,
109 SignomialEquality(y, x**2 + x)])
110 sol = m.localsolve(solver=self.solver, verbosity=0)
111 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig)
112 self.assertAlmostEqual(sol("y")[0], 0.1908254, self.ndig)
113 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig)
114 # test scalar input to sigeq
115 z = Variable("z")
116 with SignomialsEnabled():
117 m = Model(c, [c >= (x + 0.25)**2 + (z - 0.5)**2,
118 SignomialEquality(x**2 + x, z)])
119 sol = m.localsolve(solver=self.solver, verbosity=0)
120 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig)
121 self.assertAlmostEqual(sol("z"), 0.1908254, self.ndig)
122 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig)
124 def test_601(self):
125 # tautological monomials should solve but not pass to the solver
126 x = Variable("x")
127 y = Variable("y", 2)
128 m = Model(x,
129 [x >= 1,
130 y == 2])
131 m.solve(solver=self.solver, verbosity=0)
132 self.assertEqual(len(list(m.as_hmapslt1({}))), 3)
133 self.assertEqual(len(m.program.hmaps), 2)
135 def test_cost_freeing(self):
136 "Test freeing a variable that's in the cost."
137 x = Variable("x", 1)
138 x_min = Variable("x_{min}", 2)
139 intermediary = Variable("intermediary")
140 m = Model(x, [x >= intermediary, intermediary >= x_min])
141 self.assertRaises((PrimalInfeasible, UnknownInfeasible), m.solve,
142 solver=self.solver, verbosity=0)
143 x = Variable("x", 1)
144 x_min = Variable("x_{min}", 2)
145 m = Model(x, [x >= x_min])
146 self.assertRaises(PrimalInfeasible, m.solve,
147 solver=self.solver, verbosity=0)
148 del m.substitutions[m["x"]]
149 self.assertAlmostEqual(m.solve(solver=self.solver,
150 verbosity=0)["cost"], 2)
151 del m.substitutions[m["x_{min}"]]
152 self.assertRaises(UnboundedGP, m.solve,
153 solver=self.solver, verbosity=0)
154 gp = m.gp(checkbounds=False)
155 self.assertRaises(DualInfeasible, gp.solve,
156 solver=self.solver, verbosity=0)
158 def test_simple_united_gp(self):
159 R = Variable("R", "nautical_miles")
160 a0 = Variable("a0", 340.29, "m/s")
161 theta = Variable("\\theta", 0.7598)
162 t = Variable("t", 10, "hr")
163 T_loiter = Variable("T_{loiter}", 1, "hr")
164 T_reserve = Variable("T_{reserve}", 45, "min")
165 M = VectorVariable(2, "M")
167 prob = Model(1/R,
168 [t >= sum(R/a0/M/theta**0.5) + T_loiter + T_reserve,
169 M <= 0.76])
170 sol = prob.solve(solver=self.solver, verbosity=0)
171 almostequal = self.assertAlmostEqual
172 almostequal(0.000553226/sol["cost"], 1, self.ndig)
173 almostequal(340.29/sol["constants"]["a0"], 1, self.ndig)
174 almostequal(340.29/sol["variables"]["a0"], 1, self.ndig)
175 almostequal(340.29*a0.units/sol("a0"), 1, self.ndig)
176 almostequal(1807.58/sol["freevariables"]["R"], 1, self.ndig)
177 almostequal(1807.58*R.units/sol("R"), 1, self.ndig)
179 def test_trivial_vector_gp(self):
180 "Create and solve a trivial GP with VectorVariables"
181 x = VectorVariable(2, "x")
182 y = VectorVariable(2, "y")
183 prob = Model(cost=(sum(x) + 2*sum(y)),
184 constraints=[x*y >= 1])
185 sol = prob.solve(solver=self.solver, verbosity=0)
186 self.assertEqual(sol("x").shape, (2,))
187 self.assertEqual(sol("y").shape, (2,))
188 for x, y in zip(sol("x"), sol("y")):
189 self.assertAlmostEqual(x, np.sqrt(2.), self.ndig)
190 self.assertAlmostEqual(y, 1/np.sqrt(2.), self.ndig)
191 self.assertAlmostEqual(sol["cost"]/(4*np.sqrt(2)), 1., self.ndig)
193 def test_sensitivities(self):
194 W_payload = Variable("W_{payload}", 175*(195 + 30), "lbf")
195 f_oew = Variable("f_{oew}", 0.53, "-", "OEW/MTOW")
196 fuel_per_nm = Variable("\\theta_{fuel}", 13.75, "lbf/nautical_mile")
197 R = Variable("R", 3000, "nautical_miles", "range")
198 mtow = Variable("MTOW", "lbf", "max take off weight")
200 m = Model(61.3e6*units.USD*(mtow/(1e5*units.lbf))**0.807,
201 [mtow >= W_payload + f_oew*mtow + fuel_per_nm*R])
202 sol = m.solve(solver=self.solver, verbosity=0)
203 senss = sol["sensitivities"]["variables"]
204 self.assertAlmostEqual(senss[f_oew], 0.91, 2)
205 self.assertAlmostEqual(senss[R], 0.41, 2)
206 self.assertAlmostEqual(senss[fuel_per_nm], 0.41, 2)
207 self.assertAlmostEqual(senss[W_payload], 0.39, 2)
209 def test_mdd_example(self):
210 Cl = Variable("Cl", 0.5, "-", "Lift Coefficient")
211 Mdd = Variable("Mdd", "-", "Drag Divergence Mach Number")
212 m1 = Model(1/Mdd, [1 >= 5*Mdd + 0.5, Mdd >= 0.00001])
213 m2 = Model(1/Mdd, [1 >= 5*Mdd + 0.5])
214 m3 = Model(1/Mdd, [1 >= 5*Mdd + Cl, Mdd >= 0.00001])
215 sol1 = m1.solve(solver=self.solver, verbosity=0)
216 sol2 = m2.solve(solver=self.solver, verbosity=0)
217 sol3 = m3.solve(solver=self.solver, verbosity=0)
218 # pylint: disable=no-member
219 gp1, gp2, gp3 = [m.program for m in [m1, m2, m3]]
220 self.assertEqual(gp1.A, CootMatrix(row=[0, 1, 2],
221 col=[0, 0, 0],
222 data=[-1, 1, -1]))
223 self.assertEqual(gp2.A, CootMatrix(row=[0, 1],
224 col=[0, 0],
225 data=[-1, 1]))
226 self.assertEqual(gp3.A, CootMatrix(row=[0, 1, 2],
227 col=[0, 0, 0],
228 data=[-1, 1, -1]))
229 self.assertTrue((gp3.A.todense() == np.matrix([-1, 1, -1]).T).all())
230 self.assertAlmostEqual(sol1(Mdd), sol2(Mdd))
231 self.assertAlmostEqual(sol1(Mdd), sol3(Mdd))
232 self.assertAlmostEqual(sol2(Mdd), sol3(Mdd))
234 def test_additive_constants(self):
235 x = Variable("x")
236 m = Model(1/x, [1 >= 5*x + 0.5, 1 >= 5*x])
237 m.solve(verbosity=0)
238 # pylint: disable=no-member
239 gp = m.program # created by solve()
240 self.assertEqual(gp.cs[1], 2*gp.cs[2])
241 self.assertEqual(gp.A.data[1], gp.A.data[2])
243 def test_zeroing(self):
244 L = Variable("L")
245 k = Variable("k", 0)
246 with SignomialsEnabled():
247 constr = [L-5*k <= 10]
248 sol = Model(1/L, constr).solve(self.solver, verbosity=0)
249 self.assertAlmostEqual(sol(L), 10, self.ndig)
250 self.assertAlmostEqual(sol["cost"], 0.1, self.ndig)
251 self.assertTrue(sol.almost_equal(sol))
253 def test_singular(self): # pragma: no cover
254 "Create and solve GP with a singular A matrix"
255 if self.solver == "cvxopt":
256 # cvxopt can"t solve this problem
257 # (see https://github.com/cvxopt/cvxopt/issues/36)
258 return
259 x = Variable("x")
260 y = Variable("y")
261 m = Model(y*x, [y*x >= 12])
262 sol = m.solve(solver=self.solver, verbosity=0)
263 self.assertAlmostEqual(sol["cost"], 12, self.ndig)
265 def test_constants_in_objective_1(self):
266 "Issue 296"
267 x1 = Variable("x1")
268 x2 = Variable("x2")
269 m = Model(1. + x1 + x2, [x1 >= 1., x2 >= 1.])
270 sol = m.solve(solver=self.solver, verbosity=0)
271 self.assertAlmostEqual(sol["cost"], 3, self.ndig)
273 def test_constants_in_objective_2(self):
274 "Issue 296"
275 x1 = Variable("x1")
276 x2 = Variable("x2")
277 m = Model(x1**2 + 100 + 3*x2, [x1 >= 10., x2 >= 15.])
278 sol = m.solve(solver=self.solver, verbosity=0)
279 self.assertAlmostEqual(sol["cost"]/245., 1, self.ndig)
281 def test_terminating_constant_(self):
282 x = Variable("x")
283 y = Variable("y", value=0.5)
284 prob = Model(1/x, [x + y <= 4])
285 sol = prob.solve(verbosity=0)
286 self.assertAlmostEqual(sol["cost"], 1/3.5, self.ndig)
288 def test_exps_is_tuple(self):
289 "issue 407"
290 x = Variable("x")
291 m = Model(x, [x >= 1])
292 m.solve(verbosity=0)
293 self.assertEqual(type(m.program.cost.exps), tuple)
295 def test_posy_simplification(self):
296 "issue 525"
297 D = Variable("D")
298 mi = Variable("m_i")
299 V = Variable("V", 1)
300 m1 = Model(D + V, [V >= mi + 0.4, mi >= 0.1, D >= mi**2])
301 m2 = Model(D + 1, [1 >= mi + 0.4, mi >= 0.1, D >= mi**2])
302 gp1 = m1.gp()
303 gp2 = m2.gp()
304 # pylint: disable=no-member
305 self.assertEqual(gp1.A, gp2.A)
306 self.assertTrue(gp1.cs == gp2.cs)
309class TestSP(unittest.TestCase):
310 "test case for SP class -- gets run for each installed solver"
311 name = "TestSP_"
312 solver = None
313 ndig = None
315 def test_sp_relaxation(self):
316 w = Variable("w")
317 x = Variable("x")
318 y = Variable("y")
319 z = Variable("z")
320 with SignomialsEnabled():
321 m = Model(x, [x+y >= w, x+y <= z/2, y <= x, y >= 1], {z: 3, w: 3})
322 r1 = ConstantsRelaxed(m)
323 self.assertEqual(len(r1.vks), 8)
324 with self.assertRaises(ValueError):
325 _ = Model(x*r1.relaxvars, r1) # no "prod"
326 sp = Model(x*r1.relaxvars.prod()**10, r1).sp(use_pccp=False)
327 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"]
328 self.assertAlmostEqual(cost/1024, 1, self.ndig)
329 m.debug(verbosity=0, solver=self.solver)
330 with SignomialsEnabled():
331 m = Model(x, [x+y >= z, x+y <= z/2, y <= x, y >= 1], {z: 3})
332 m.debug(verbosity=0, solver=self.solver)
333 r2 = ConstraintsRelaxed(m)
334 self.assertEqual(len(r2.vks), 7)
335 sp = Model(x*r2.relaxvars.prod()**10, r2).sp(use_pccp=False)
336 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"]
337 self.assertAlmostEqual(cost/1024, 1, self.ndig)
338 with SignomialsEnabled():
339 m = Model(x, [x+y >= z, x+y <= z/2, y <= x, y >= 1], {z: 3})
340 m.debug(verbosity=0, solver=self.solver)
341 r3 = ConstraintsRelaxedEqually(m)
342 self.assertEqual(len(r3.vks), 4)
343 sp = Model(x*r3.relaxvar**10, r3).sp(use_pccp=False)
344 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"]
345 self.assertAlmostEqual(cost/(32*0.8786796585), 1, self.ndig)
347 def test_sp_bounded(self):
348 x = Variable("x")
349 y = Variable("y")
351 with SignomialsEnabled():
352 m = Model(x, [x + y >= 1, y <= 0.1]) # solves
353 cost = m.localsolve(verbosity=0, solver=self.solver)["cost"]
354 self.assertAlmostEqual(cost, 0.9, self.ndig)
356 with SignomialsEnabled():
357 m = Model(x, [x + y >= 1]) # dual infeasible
358 with self.assertRaises(UnboundedGP):
359 m.localsolve(verbosity=0, solver=self.solver)
360 gp = m.sp(checkbounds=False).gp()
361 self.assertRaises(DualInfeasible, gp.solve,
362 solver=self.solver, verbosity=0)
364 with SignomialsEnabled():
365 m = Model(x, Bounded([x + y >= 1]))
366 sol = m.localsolve(verbosity=0, solver=self.solver)
367 boundedness = sol["boundedness"]
368 # depends on solver, platform, whims of the numerical deities
369 if "value near lower bound of 1e-30" in boundedness: # pragma: no cover
370 self.assertIn(x.key, boundedness["value near lower bound of 1e-30"])
371 else: # pragma: no cover
372 self.assertIn(y.key, boundedness["value near upper bound of 1e+30"])
374 def test_values_vs_subs(self):
375 # Substitutions update method
376 x = Variable("x")
377 y = Variable("y")
378 z = Variable("z")
380 with SignomialsEnabled():
381 constraints = [x + y >= z,
382 y >= x - 1]
383 m = Model(x + y*z, constraints)
384 m.substitutions.update({"z": 5})
385 sol = m.localsolve(verbosity=0, solver=self.solver)
386 self.assertAlmostEqual(sol["cost"], 13, self.ndig)
388 # Constant variable declaration method
389 z = Variable("z", 5)
390 with SignomialsEnabled():
391 constraints = [x + y >= z,
392 y >= x - 1]
393 m = Model(x + y*z, constraints)
394 sol = m.localsolve(verbosity=0, solver=self.solver)
395 self.assertAlmostEqual(sol["cost"], 13, self.ndig)
397 def test_initially_infeasible(self):
398 x = Variable("x")
399 y = Variable("y")
401 with SignomialsEnabled():
402 sigc = x >= y + y**2 - y**3
403 sigc2 = x <= y**0.5
405 m = Model(1/x, [sigc, sigc2, y <= 0.5])
407 sol = m.localsolve(verbosity=0, solver=self.solver)
408 self.assertAlmostEqual(sol["cost"], 2**0.5, self.ndig)
409 self.assertAlmostEqual(sol(y), 0.5, self.ndig)
411 def test_sp_substitutions(self):
412 x = Variable("x")
413 y = Variable("y", 1)
414 z = Variable("z", 4)
416 from io import StringIO
417 old_stdout = sys.stdout
418 sys.stdout = stringout = StringIO()
420 with SignomialsEnabled():
421 m1 = Model(x, [x + z >= y])
422 with self.assertRaises(UnnecessarySGP):
423 m1.localsolve(verbosity=0, solver=self.solver)
424 with self.assertRaises(UnboundedGP):
425 m1.solve(verbosity=0, solver=self.solver)
427 with SignomialsEnabled():
428 m2 = Model(x, [x + y >= z])
429 m2.substitutions[y] = 1
430 m2.substitutions[z] = 4
431 sol = m2.solve(self.solver, verbosity=0)
432 self.assertAlmostEqual(sol["cost"], 3, self.ndig)
434 sys.stdout = old_stdout
435 self.assertEqual(stringout.getvalue(), (
436 "Warning: SignomialConstraint %s became the tautological"
437 " constraint 0 <= 3 + x after substitution.\n"
438 "Warning: SignomialConstraint %s became the tautological"
439 " constraint 0 <= 3 + x after substitution.\n"
440 % (str(m1[0]), str(m1[0]))))
442 def test_tautological(self):
443 x = Variable("x")
444 y = Variable("y")
445 z = Variable("z")
447 from io import StringIO
448 old_stdout = sys.stdout
449 sys.stdout = stringout = StringIO()
451 with SignomialsEnabled():
452 m1 = Model(x, [x + y >= z, x >= y])
453 m2 = Model(x, [x + 1 >= 0, x >= y])
454 m1.substitutions.update({"z": 0, "y": 1})
455 m2.substitutions.update({"y": 1})
456 self.assertAlmostEqual(m1.solve(self.solver, verbosity=0)["cost"],
457 m2.solve(self.solver, verbosity=0)["cost"])
459 sys.stdout = old_stdout
460 self.assertEqual(stringout.getvalue(), (
461 "Warning: SignomialConstraint %s became the tautological"
462 " constraint 0 <= 1 + x after substitution.\n"
463 "Warning: SignomialConstraint %s became the tautological"
464 " constraint 0 <= 1 + x after substitution.\n"
465 % (str(m1[0]), str(m2[0]))))
467 def test_impossible(self):
468 x = Variable("x")
469 y = Variable("y")
470 z = Variable("z")
472 with SignomialsEnabled():
473 m1 = Model(x, [x + y >= z, x >= y])
474 m1.substitutions.update({"x": 0, "y": 0})
475 with self.assertRaises(ValueError):
476 _ = m1.localsolve(solver=self.solver)
478 def test_trivial_sp(self):
479 x = Variable("x")
480 y = Variable("y")
481 with SignomialsEnabled():
482 m = Model(x, [x >= 1-y, y <= 0.1])
483 with self.assertRaises(InvalidGPConstraint):
484 m.solve(verbosity=0, solver=self.solver)
485 sol = m.localsolve(self.solver, verbosity=0)
486 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig)
487 with SignomialsEnabled():
488 m = Model(x, [x+y >= 1, y <= 0.1])
489 sol = m.localsolve(self.solver, verbosity=0)
490 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig)
492 def test_tautological_spconstraint(self):
493 x = Variable("x")
494 y = Variable("y")
495 z = Variable("z", 0)
496 with SignomialsEnabled():
497 m = Model(x, [x >= 1-y, y <= 0.1, y >= z])
498 with self.assertRaises(InvalidGPConstraint):
499 m.solve(verbosity=0, solver=self.solver)
500 sol = m.localsolve(self.solver, verbosity=0)
501 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig)
503 def test_relaxation(self):
504 x = Variable("x")
505 y = Variable("y")
506 with SignomialsEnabled():
507 constraints = [y + x >= 2, y <= x]
508 objective = x
509 m = Model(objective, constraints)
510 m.localsolve(verbosity=0, solver=self.solver)
512 # issue #257
514 A = VectorVariable(2, "A")
515 B = ArrayVariable([2, 2], "B")
516 C = VectorVariable(2, "C")
517 with SignomialsEnabled():
518 constraints = [A <= B.dot(C),
519 B <= 1,
520 C <= 1]
521 obj = 1/A[0] + 1/A[1]
522 m = Model(obj, constraints)
523 m.localsolve(verbosity=0, solver=self.solver)
525 def test_issue180(self):
526 L = Variable("L")
527 Lmax = Variable("L_{max}", 10)
528 W = Variable("W")
529 Wmax = Variable("W_{max}", 10)
530 A = Variable("A", 10)
531 Obj = Variable("Obj")
532 a_val = 0.01
533 a = Variable("a", a_val)
534 with SignomialsEnabled():
535 eqns = [L <= Lmax,
536 W <= Wmax,
537 L*W >= A,
538 Obj >= a*(2*L + 2*W) + (1-a)*(12 * W**-1 * L**-3)]
539 m = Model(Obj, eqns)
540 spsol = m.solve(self.solver, verbosity=0)
541 # now solve as GP
542 m[-1] = (Obj >= a_val*(2*L + 2*W) + (1-a_val)*(12 * W**-1 * L**-3))
543 del m.substitutions[m["a"]]
544 gpsol = m.solve(self.solver, verbosity=0)
545 self.assertAlmostEqual(spsol["cost"], gpsol["cost"])
547 def test_trivial_sp2(self):
548 x = Variable("x")
549 y = Variable("y")
551 # converging from above
552 with SignomialsEnabled():
553 constraints = [y + x >= 2, y >= x]
554 objective = y
555 x0 = 1
556 y0 = 2
557 m = Model(objective, constraints)
558 sol1 = m.localsolve(x0={x: x0, y: y0}, verbosity=0, solver=self.solver)
560 # converging from right
561 with SignomialsEnabled():
562 constraints = [y + x >= 2, y <= x]
563 objective = x
564 x0 = 2
565 y0 = 1
566 m = Model(objective, constraints)
567 sol2 = m.localsolve(x0={x: x0, y: y0}, verbosity=0, solver=self.solver)
569 self.assertAlmostEqual(sol1["variables"]["x"],
570 sol2["variables"]["x"], self.ndig)
571 self.assertAlmostEqual(sol1["variables"]["y"],
572 sol2["variables"]["x"], self.ndig)
574 def test_sp_initial_guess_sub(self):
575 x = Variable("x")
576 y = Variable("y")
577 x0 = 3
578 y0 = 2
579 with SignomialsEnabled():
580 constraints = [y + x >= 4, y <= x]
581 objective = x
582 m = Model(objective, constraints)
583 # Call to local solve with only variables
584 sol = m.localsolve(x0={"x": x0, y: y0}, verbosity=0,
585 solver=self.solver)
586 self.assertAlmostEqual(sol(x), 2, self.ndig)
587 self.assertAlmostEqual(sol["cost"], 2, self.ndig)
589 # Call to local solve with only variable strings
590 sol = m.localsolve(x0={"x": x0, "y": y0}, verbosity=0,
591 solver=self.solver)
592 self.assertAlmostEqual(sol("x"), 2, self.ndig)
593 self.assertAlmostEqual(sol["cost"], 2, self.ndig)
595 # Call to local solve with a mix of variable strings and variables
596 sol = m.localsolve(x0={"x": x0, y: y0}, verbosity=0,
597 solver=self.solver)
598 self.assertAlmostEqual(sol["cost"], 2, self.ndig)
600 def test_small_named_signomial(self):
601 x = Variable("x")
602 z = Variable("z")
603 local_ndig = 4
604 nonzero_adder = 0.1
605 with SignomialsEnabled():
606 J = 0.01*(x - 1)**2 + nonzero_adder
607 with NamedVariables("SmallSignomial"):
608 m = Model(z, [z >= J])
609 sol = m.localsolve(verbosity=0, solver=self.solver)
610 self.assertAlmostEqual(sol["cost"], nonzero_adder, local_ndig)
611 self.assertAlmostEqual(sol("x"), 0.98725425, self.ndig)
613 def test_sigs_not_allowed_in_cost(self):
614 with SignomialsEnabled():
615 x = Variable("x")
616 y = Variable("y")
617 J = 0.01*((x - 1)**2 + (y - 1)**2) + (x*y - 1)**2
618 m = Model(J)
619 with self.assertRaises(InvalidPosynomial):
620 m.localsolve(verbosity=0, solver=self.solver)
622 def test_partial_sub_signomial(self):
623 "Test SP partial x0 initialization"
624 x = Variable("x")
625 y = Variable("y")
626 with SignomialsEnabled():
627 m = Model(x, [x + y >= 1, y <= 0.5])
628 gp = m.sp().gp(x0={x: 0.5}) # pylint: disable=no-member
629 first_gp_constr_posy_exp, = gp.hmaps[1] # first after cost
630 self.assertEqual(first_gp_constr_posy_exp[x.key], -1./3)
632 def test_becomes_signomial(self):
633 "Test that a GP does not become an SP after substitutions"
634 x = Variable("x")
635 c = Variable("c")
636 y = Variable("y")
637 m = Model(x, [y >= 1 + c*x, y <= 0.5], {c: -1})
638 with self.assertRaises(InvalidGPConstraint):
639 with SignomialsEnabled():
640 m.gp()
641 with self.assertRaises(UnnecessarySGP):
642 m.localsolve(solver=self.solver)
644 def test_reassigned_constant_cost(self):
645 # for issue 1131
646 x = Variable("x")
647 x_min = Variable("x_min", 1)
648 y = Variable("y")
649 with SignomialsEnabled():
650 m = Model(y, [y + 0.5 >= x, x >= x_min, 6 >= y])
651 m.localsolve(verbosity=0, solver=self.solver)
652 del m.substitutions[x_min]
653 m.cost = 1/x_min
654 self.assertNotIn(x_min, m.sp().gp().substitutions) # pylint: disable=no-member
656 def test_unbounded_debugging(self):
657 "Test nearly-dual-feasible problems"
658 x = Variable("x")
659 y = Variable("y")
660 m = Model(x*y, [x*y**1.01 >= 100])
661 with self.assertRaises((DualInfeasible, UnknownInfeasible)):
662 m.solve(self.solver, verbosity=0)
663 # test one-sided bound
664 m = Model(x*y, Bounded(m, lower=0.001))
665 sol = m.solve(self.solver, verbosity=0)
666 bounds = sol["boundedness"]
667 self.assertEqual(bounds["sensitive to lower bound of 0.001"],
668 set([x.key]))
669 # end test one-sided bound
670 m = Model(x*y, [x*y**1.01 >= 100])
671 m = Model(x*y, Bounded(m))
672 sol = m.solve(self.solver, verbosity=0)
673 bounds = sol["boundedness"]
674 # depends on solver, platform, whims of the numerical deities
675 if "sensitive to upper bound of 1e+30" in bounds: # pragma: no cover
676 self.assertIn(y.key, bounds["sensitive to upper bound of 1e+30"])
677 else: # pragma: no cover
678 self.assertIn(x.key, bounds["sensitive to lower bound of 1e-30"])
681class TestModelSolverSpecific(unittest.TestCase):
682 "test cases run only for specific solvers"
683 def test_cvxopt_kwargs(self): # pragma: no cover
684 if "cvxopt" not in settings["installed_solvers"]:
685 return
686 x = Variable("x")
687 m = Model(x, [x >= 12])
688 # make sure it"s possible to pass the kktsolver option to cvxopt
689 sol = m.solve(solver="cvxopt", verbosity=0, kktsolver="ldl")
690 self.assertAlmostEqual(sol["cost"], 12., NDIGS["cvxopt"])
693class Thing(Model):
694 "a thing, for model testing"
695 def setup(self, length):
696 a = self.a = VectorVariable(length, "a", "g/m")
697 b = self.b = VectorVariable(length, "b", "m")
698 c = Variable("c", 17/4., "g")
699 return [a >= c/b]
702class Thing2(Model):
703 "another thing for model testing"
704 def setup(self):
705 return [Thing(2), Model()]
708class Box(Model):
709 """simple box for model testing
711 Variables
712 ---------
713 h [m] height
714 w [m] width
715 d [m] depth
716 V [m**3] volume
718 Upper Unbounded
719 ---------------
720 w, d, h
722 Lower Unbounded
723 ---------------
724 w, d, h
725 """
726 @parse_variables(__doc__, globals())
727 def setup(self):
728 return [V == h*w*d]
731class BoxAreaBounds(Model):
732 """for testing functionality of separate analysis models
734 Lower Unbounded
735 ---------------
736 h, d, w
737 """
738 def setup(self, box):
739 A_wall = Variable("A_{wall}", 100, "m^2", "Upper limit, wall area")
740 A_floor = Variable("A_{floor}", 50, "m^2", "Upper limit, floor area")
742 self.h, self.d, self.w = box.h, box.d, box.w
744 return [2*box.h*box.w + 2*box.h*box.d <= A_wall,
745 box.w*box.d <= A_floor]
748class Sub(Model):
749 "Submodel with mass, for testing"
750 def setup(self):
751 m = Variable("m", "lb", "mass")
754class Widget(Model):
755 "A model with two Sub models"
756 def setup(self):
757 m_tot = Variable("m_{tot}", "lb", "total mass")
758 self.subA = Sub()
759 self.subB = Sub()
760 return [self.subA, self.subB,
761 m_tot >= self.subA["m"] + self.subB["m"]]
764class TestModelNoSolve(unittest.TestCase):
765 "model tests that don't require a solver"
766 def test_modelname_added(self):
767 t = Thing(2)
768 for vk in t.vks:
769 self.assertEqual(vk.lineage, (("Thing", 0),))
771 def test_modelcontainmentprinting(self):
772 t = Thing2()
773 self.assertEqual(t["c"].key.models, ("Thing2", "Thing"))
774 self.assertIsInstance(t.str_without(), str)
775 self.assertIsInstance(t.latex(), str)
777 def test_no_naming_on_var_access(self):
778 # make sure that analysis models don't add their names to
779 # variables looked up from other models
780 box = Box()
781 area_bounds = BoxAreaBounds(box)
782 M = Model(box["V"], [box, area_bounds])
783 for var in ("h", "w", "d"):
784 self.assertEqual(len(M.variables_byname(var)), 1)
786 def test_duplicate_submodel_varnames(self):
787 w = Widget()
788 # w has two Sub models, both with their own variable m
789 self.assertEqual(len(w.variables_byname("m")), 2)
790 # keys for both submodel m's should be in the parent model varkeys
791 self.assertIn(w.subA["m"].key, w.varkeys)
792 self.assertIn(w.subB["m"].key, w.varkeys)
793 # keys of w.variables_byname("m") should match m.varkeys
794 m_vbn_keys = [v.key for v in w.variables_byname("m")]
795 self.assertIn(w.subA["m"].key, m_vbn_keys)
796 self.assertIn(w.subB["m"].key, m_vbn_keys)
797 # dig a level deeper, into the keymap
798 self.assertEqual(len(w.varkeys.keymap["m"]), 2)
799 w2 = Widget()
802TESTS = [TestModelSolverSpecific, TestModelNoSolve]
803MULTI_SOLVER_TESTS = [TestGP, TestSP]
805for testcase in MULTI_SOLVER_TESTS:
806 for solver in settings["installed_solvers"]:
807 if solver:
808 test = type(str(testcase.__name__+"_"+solver),
809 (testcase,), {})
810 setattr(test, "solver", solver)
811 setattr(test, "ndig", NDIGS[solver])
812 TESTS.append(test)
814if __name__ == "__main__": # pragma: no cover
815 # pylint: disable=wrong-import-position
816 from gpkit.tests.helpers import run_tests
817 run_tests(TESTS, verbosity=0)