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_sigeq(self):
63 x = Variable("x")
64 y = VectorVariable(1, "y")
65 c = Variable("c")
66 # test left vector input to sigeq
67 with SignomialsEnabled():
68 m = Model(c, [c >= (x + 0.25)**2 + (y - 0.5)**2,
69 SignomialEquality(x**2 + x, y)])
70 sol = m.localsolve(solver=self.solver, verbosity=0)
71 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig)
72 self.assertAlmostEqual(sol("y")[0], 0.1908254, self.ndig)
73 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig)
74 # test right vector input to sigeq
75 with SignomialsEnabled():
76 m = Model(c, [c >= (x + 0.25)**2 + (y - 0.5)**2,
77 SignomialEquality(y, x**2 + x)])
78 sol = m.localsolve(solver=self.solver, verbosity=0)
79 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig)
80 self.assertAlmostEqual(sol("y")[0], 0.1908254, self.ndig)
81 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig)
82 # test scalar input to sigeq
83 z = Variable("z")
84 with SignomialsEnabled():
85 m = Model(c, [c >= (x + 0.25)**2 + (z - 0.5)**2,
86 SignomialEquality(x**2 + x, z)])
87 sol = m.localsolve(solver=self.solver, verbosity=0)
88 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig)
89 self.assertAlmostEqual(sol("z"), 0.1908254, self.ndig)
90 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig)
92 def test_601(self):
93 # tautological monomials should solve but not pass to the solver
94 x = Variable("x")
95 y = Variable("y", 2)
96 m = Model(x,
97 [x >= 1,
98 y == 2])
99 m.solve(solver=self.solver, verbosity=0)
100 self.assertEqual(len(list(m.as_hmapslt1({}))), 3)
101 self.assertEqual(len(m.program.hmaps), 2)
103 def test_cost_freeing(self):
104 "Test freeing a variable that's in the cost."
105 x = Variable("x", 1)
106 x_min = Variable("x_{min}", 2)
107 intermediary = Variable("intermediary")
108 m = Model(x, [x >= intermediary, intermediary >= x_min])
109 self.assertRaises((PrimalInfeasible, UnknownInfeasible), m.solve,
110 solver=self.solver, verbosity=0)
111 x = Variable("x", 1)
112 x_min = Variable("x_{min}", 2)
113 m = Model(x, [x >= x_min])
114 self.assertRaises(PrimalInfeasible, m.solve,
115 solver=self.solver, verbosity=0)
116 del m.substitutions[m["x"]]
117 self.assertAlmostEqual(m.solve(solver=self.solver,
118 verbosity=0)["cost"], 2)
119 del m.substitutions[m["x_{min}"]]
120 self.assertRaises(UnboundedGP, m.solve,
121 solver=self.solver, verbosity=0)
122 gp = m.gp(checkbounds=False)
123 self.assertRaises(DualInfeasible, gp.solve,
124 solver=self.solver, verbosity=0)
126 def test_simple_united_gp(self):
127 R = Variable("R", "nautical_miles")
128 a0 = Variable("a0", 340.29, "m/s")
129 theta = Variable("\\theta", 0.7598)
130 t = Variable("t", 10, "hr")
131 T_loiter = Variable("T_{loiter}", 1, "hr")
132 T_reserve = Variable("T_{reserve}", 45, "min")
133 M = VectorVariable(2, "M")
135 prob = Model(1/R,
136 [t >= sum(R/a0/M/theta**0.5) + T_loiter + T_reserve,
137 M <= 0.76])
138 sol = prob.solve(solver=self.solver, verbosity=0)
139 almostequal = self.assertAlmostEqual
140 almostequal(0.000553226/sol["cost"], 1, self.ndig)
141 almostequal(340.29/sol["constants"]["a0"], 1, self.ndig)
142 almostequal(340.29/sol["variables"]["a0"], 1, self.ndig)
143 almostequal(340.29*a0.units/sol("a0"), 1, self.ndig)
144 almostequal(1807.58/sol["freevariables"]["R"], 1, self.ndig)
145 almostequal(1807.58*R.units/sol("R"), 1, self.ndig)
147 def test_trivial_vector_gp(self):
148 "Create and solve a trivial GP with VectorVariables"
149 x = VectorVariable(2, "x")
150 y = VectorVariable(2, "y")
151 prob = Model(cost=(sum(x) + 2*sum(y)),
152 constraints=[x*y >= 1])
153 sol = prob.solve(solver=self.solver, verbosity=0)
154 self.assertEqual(sol("x").shape, (2,))
155 self.assertEqual(sol("y").shape, (2,))
156 for x, y in zip(sol("x"), sol("y")):
157 self.assertAlmostEqual(x, np.sqrt(2.), self.ndig)
158 self.assertAlmostEqual(y, 1/np.sqrt(2.), self.ndig)
159 self.assertAlmostEqual(sol["cost"]/(4*np.sqrt(2)), 1., self.ndig)
161 def test_sensitivities(self):
162 W_payload = Variable("W_{payload}", 175*(195 + 30), "lbf")
163 f_oew = Variable("f_{oew}", 0.53, "-", "OEW/MTOW")
164 fuel_per_nm = Variable("\\theta_{fuel}", 13.75, "lbf/nautical_mile")
165 R = Variable("R", 3000, "nautical_miles", "range")
166 mtow = Variable("MTOW", "lbf", "max take off weight")
168 m = Model(61.3e6*units.USD*(mtow/(1e5*units.lbf))**0.807,
169 [mtow >= W_payload + f_oew*mtow + fuel_per_nm*R])
170 sol = m.solve(solver=self.solver, verbosity=0)
171 senss = sol["sensitivities"]["variables"]
172 self.assertAlmostEqual(senss[f_oew], 0.91, 2)
173 self.assertAlmostEqual(senss[R], 0.41, 2)
174 self.assertAlmostEqual(senss[fuel_per_nm], 0.41, 2)
175 self.assertAlmostEqual(senss[W_payload], 0.39, 2)
177 def test_mdd_example(self):
178 Cl = Variable("Cl", 0.5, "-", "Lift Coefficient")
179 Mdd = Variable("Mdd", "-", "Drag Divergence Mach Number")
180 m1 = Model(1/Mdd, [1 >= 5*Mdd + 0.5, Mdd >= 0.00001])
181 m2 = Model(1/Mdd, [1 >= 5*Mdd + 0.5])
182 m3 = Model(1/Mdd, [1 >= 5*Mdd + Cl, Mdd >= 0.00001])
183 sol1 = m1.solve(solver=self.solver, verbosity=0)
184 sol2 = m2.solve(solver=self.solver, verbosity=0)
185 sol3 = m3.solve(solver=self.solver, verbosity=0)
186 # pylint: disable=no-member
187 gp1, gp2, gp3 = [m.program for m in [m1, m2, m3]]
188 self.assertEqual(gp1.A, CootMatrix(row=[0, 1, 2],
189 col=[0, 0, 0],
190 data=[-1, 1, -1]))
191 self.assertEqual(gp2.A, CootMatrix(row=[0, 1],
192 col=[0, 0],
193 data=[-1, 1]))
194 self.assertEqual(gp3.A, CootMatrix(row=[0, 1, 2],
195 col=[0, 0, 0],
196 data=[-1, 1, -1]))
197 self.assertTrue((gp3.A.todense() == np.matrix([-1, 1, -1]).T).all())
198 self.assertAlmostEqual(sol1(Mdd), sol2(Mdd))
199 self.assertAlmostEqual(sol1(Mdd), sol3(Mdd))
200 self.assertAlmostEqual(sol2(Mdd), sol3(Mdd))
202 def test_additive_constants(self):
203 x = Variable("x")
204 m = Model(1/x, [1 >= 5*x + 0.5, 1 >= 10*x])
205 m.solve(verbosity=0)
206 # pylint: disable=no-member
207 gp = m.program # created by solve()
208 self.assertEqual(gp.cs[1], gp.cs[2])
209 self.assertEqual(gp.A.data[1], gp.A.data[2])
211 def test_zeroing(self):
212 L = Variable("L")
213 k = Variable("k", 0)
214 with SignomialsEnabled():
215 constr = [L-5*k <= 10]
216 sol = Model(1/L, constr).solve(self.solver, verbosity=0)
217 self.assertAlmostEqual(sol(L), 10, self.ndig)
218 self.assertAlmostEqual(sol["cost"], 0.1, self.ndig)
219 self.assertTrue(sol.almost_equal(sol))
221 def test_singular(self): # pragma: no cover
222 "Create and solve GP with a singular A matrix"
223 if self.solver == "cvxopt":
224 # cvxopt can"t solve this problem
225 # (see https://github.com/cvxopt/cvxopt/issues/36)
226 return
227 x = Variable("x")
228 y = Variable("y")
229 m = Model(y*x, [y*x >= 12])
230 sol = m.solve(solver=self.solver, verbosity=0)
231 self.assertAlmostEqual(sol["cost"], 12, self.ndig)
233 def test_constants_in_objective_1(self):
234 "Issue 296"
235 x1 = Variable("x1")
236 x2 = Variable("x2")
237 m = Model(1. + x1 + x2, [x1 >= 1., x2 >= 1.])
238 sol = m.solve(solver=self.solver, verbosity=0)
239 self.assertAlmostEqual(sol["cost"], 3, self.ndig)
241 def test_constants_in_objective_2(self):
242 "Issue 296"
243 x1 = Variable("x1")
244 x2 = Variable("x2")
245 m = Model(x1**2 + 100 + 3*x2, [x1 >= 10., x2 >= 15.])
246 sol = m.solve(solver=self.solver, verbosity=0)
247 self.assertAlmostEqual(sol["cost"]/245., 1, self.ndig)
249 def test_terminating_constant_(self):
250 x = Variable("x")
251 y = Variable("y", value=0.5)
252 prob = Model(1/x, [x + y <= 4])
253 sol = prob.solve(verbosity=0)
254 self.assertAlmostEqual(sol["cost"], 1/3.5, self.ndig)
256 def test_exps_is_tuple(self):
257 "issue 407"
258 x = Variable("x")
259 m = Model(x, [x >= 1])
260 m.solve(verbosity=0)
261 self.assertEqual(type(m.program.cost.exps), tuple)
263 def test_posy_simplification(self):
264 "issue 525"
265 D = Variable("D")
266 mi = Variable("m_i")
267 V = Variable("V", 1)
268 m1 = Model(D + V, [V >= mi + 0.4, mi >= 0.1, D >= mi**2])
269 m2 = Model(D + 1, [1 >= mi + 0.4, mi >= 0.1, D >= mi**2])
270 gp1 = m1.gp()
271 gp2 = m2.gp()
272 # pylint: disable=no-member
273 self.assertEqual(gp1.A, gp2.A)
274 self.assertTrue(gp1.cs == gp2.cs)
277class TestSP(unittest.TestCase):
278 "test case for SP class -- gets run for each installed solver"
279 name = "TestSP_"
280 solver = None
281 ndig = None
283 def test_sp_relaxation(self):
284 w = Variable("w")
285 x = Variable("x")
286 y = Variable("y")
287 z = Variable("z")
288 with SignomialsEnabled():
289 m = Model(x, [x+y >= w, x+y <= z/2, y <= x, y >= 1], {z: 3, w: 3})
290 r1 = ConstantsRelaxed(m)
291 self.assertEqual(len(r1.varkeys), 8)
292 with self.assertRaises(ValueError):
293 _ = Model(x*r1.relaxvars, r1) # no "prod"
294 sp = Model(x*r1.relaxvars.prod()**10, r1).sp(use_pccp=False)
295 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"]
296 self.assertAlmostEqual(cost/1024, 1, self.ndig)
297 m.debug(verbosity=0, solver=self.solver)
298 with SignomialsEnabled():
299 m = Model(x, [x+y >= z, x+y <= z/2, y <= x, y >= 1], {z: 3})
300 m.debug(verbosity=0, solver=self.solver)
301 r2 = ConstraintsRelaxed(m)
302 self.assertEqual(len(r2.varkeys), 7)
303 sp = Model(x*r2.relaxvars.prod()**10, r2).sp(use_pccp=False)
304 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"]
305 self.assertAlmostEqual(cost/1024, 1, self.ndig)
306 with SignomialsEnabled():
307 m = Model(x, [x+y >= z, x+y <= z/2, y <= x, y >= 1], {z: 3})
308 m.debug(verbosity=0, solver=self.solver)
309 r3 = ConstraintsRelaxedEqually(m)
310 self.assertEqual(len(r3.varkeys), 4)
311 sp = Model(x*r3.relaxvar**10, r3).sp(use_pccp=False)
312 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"]
313 self.assertAlmostEqual(cost/(32*0.8786796585), 1, self.ndig)
315 def test_sp_bounded(self):
316 x = Variable("x")
317 y = Variable("y")
319 with SignomialsEnabled():
320 m = Model(x, [x + y >= 1, y <= 0.1]) # solves
321 cost = m.localsolve(verbosity=0, solver=self.solver)["cost"]
322 self.assertAlmostEqual(cost, 0.9, self.ndig)
324 with SignomialsEnabled():
325 m = Model(x, [x + y >= 1]) # dual infeasible
326 with self.assertRaises(UnboundedGP):
327 m.localsolve(verbosity=0, solver=self.solver)
328 gp = m.sp(checkbounds=False).gp()
329 self.assertRaises(DualInfeasible, gp.solve,
330 solver=self.solver, verbosity=0)
332 with SignomialsEnabled():
333 m = Model(x, Bounded([x + y >= 1]))
334 sol = m.localsolve(verbosity=0, solver=self.solver)
335 boundedness = sol["boundedness"]
336 # depends on solver, platform, whims of the numerical deities
337 if "value near lower bound of 1e-30" in boundedness: # pragma: no cover
338 self.assertIn(x.key, boundedness["value near lower bound of 1e-30"])
339 else: # pragma: no cover
340 self.assertIn(y.key, boundedness["value near upper bound of 1e+30"])
342 def test_values_vs_subs(self):
343 # Substitutions update method
344 x = Variable("x")
345 y = Variable("y")
346 z = Variable("z")
348 with SignomialsEnabled():
349 constraints = [x + y >= z,
350 y >= x - 1]
351 m = Model(x + y*z, constraints)
352 m.substitutions.update({"z": 5})
353 sol = m.localsolve(verbosity=0, solver=self.solver)
354 self.assertAlmostEqual(sol["cost"], 13, self.ndig)
356 # Constant variable declaration method
357 z = Variable("z", 5)
358 with SignomialsEnabled():
359 constraints = [x + y >= z,
360 y >= x - 1]
361 m = Model(x + y*z, constraints)
362 sol = m.localsolve(verbosity=0, solver=self.solver)
363 self.assertAlmostEqual(sol["cost"], 13, self.ndig)
365 def test_initially_infeasible(self):
366 x = Variable("x")
367 y = Variable("y")
369 with SignomialsEnabled():
370 sigc = x >= y + y**2 - y**3
371 sigc2 = x <= y**0.5
373 m = Model(1/x, [sigc, sigc2, y <= 0.5])
375 sol = m.localsolve(verbosity=0, solver=self.solver)
376 self.assertAlmostEqual(sol["cost"], 2**0.5, self.ndig)
377 self.assertAlmostEqual(sol(y), 0.5, self.ndig)
379 def test_sp_substitutions(self):
380 x = Variable("x")
381 y = Variable("y", 1)
382 z = Variable("z", 4)
384 from io import StringIO
385 old_stdout = sys.stdout
386 sys.stdout = stringout = StringIO()
388 with SignomialsEnabled():
389 m1 = Model(x, [x + z >= y])
390 with self.assertRaises(UnnecessarySGP):
391 m1.localsolve(verbosity=0, solver=self.solver)
392 with self.assertRaises(UnboundedGP):
393 m1.solve(verbosity=0, solver=self.solver)
395 with SignomialsEnabled():
396 m2 = Model(x, [x + y >= z])
397 m2.substitutions[y] = 1
398 m2.substitutions[z] = 4
399 sol = m2.solve(self.solver, verbosity=0)
400 self.assertAlmostEqual(sol["cost"], 3, self.ndig)
402 sys.stdout = old_stdout
403 self.assertEqual(stringout.getvalue(), (
404 "Warning: SignomialConstraint %s became the tautological"
405 " constraint 0 <= 3 + x after substitution.\n"
406 "Warning: SignomialConstraint %s became the tautological"
407 " constraint 0 <= 3 + x after substitution.\n"
408 % (str(m1[0]), str(m1[0]))))
410 def test_tautological(self):
411 x = Variable("x")
412 y = Variable("y")
413 z = Variable("z")
415 from io import StringIO
416 old_stdout = sys.stdout
417 sys.stdout = stringout = StringIO()
419 with SignomialsEnabled():
420 m1 = Model(x, [x + y >= z, x >= y])
421 m2 = Model(x, [x + 1 >= 0, x >= y])
422 m1.substitutions.update({"z": 0, "y": 1})
423 m2.substitutions.update({"y": 1})
424 self.assertAlmostEqual(m1.solve(self.solver, verbosity=0)["cost"],
425 m2.solve(self.solver, verbosity=0)["cost"])
427 sys.stdout = old_stdout
428 self.assertEqual(stringout.getvalue(), (
429 "Warning: SignomialConstraint %s became the tautological"
430 " constraint 0 <= 1 + x after substitution.\n"
431 "Warning: SignomialConstraint %s became the tautological"
432 " constraint 0 <= 1 + x after substitution.\n"
433 % (str(m1[0]), str(m2[0]))))
435 def test_impossible(self):
436 x = Variable("x")
437 y = Variable("y")
438 z = Variable("z")
440 with SignomialsEnabled():
441 m1 = Model(x, [x + y >= z, x >= y])
442 m1.substitutions.update({"x": 0, "y": 0})
443 with self.assertRaises(ValueError):
444 _ = m1.localsolve(solver=self.solver)
446 def test_trivial_sp(self):
447 x = Variable("x")
448 y = Variable("y")
449 with SignomialsEnabled():
450 m = Model(x, [x >= 1-y, y <= 0.1])
451 with self.assertRaises(InvalidGPConstraint):
452 m.solve(verbosity=0, solver=self.solver)
453 sol = m.localsolve(self.solver, verbosity=0)
454 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig)
455 with SignomialsEnabled():
456 m = Model(x, [x+y >= 1, y <= 0.1])
457 sol = m.localsolve(self.solver, verbosity=0)
458 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig)
460 def test_tautological_spconstraint(self):
461 x = Variable("x")
462 y = Variable("y")
463 z = Variable("z", 0)
464 with SignomialsEnabled():
465 m = Model(x, [x >= 1-y, y <= 0.1, y >= z])
466 with self.assertRaises(InvalidGPConstraint):
467 m.solve(verbosity=0, solver=self.solver)
468 sol = m.localsolve(self.solver, verbosity=0)
469 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig)
471 def test_relaxation(self):
472 x = Variable("x")
473 y = Variable("y")
474 with SignomialsEnabled():
475 constraints = [y + x >= 2, y <= x]
476 objective = x
477 m = Model(objective, constraints)
478 m.localsolve(verbosity=0, solver=self.solver)
480 # issue #257
482 A = VectorVariable(2, "A")
483 B = ArrayVariable([2, 2], "B")
484 C = VectorVariable(2, "C")
485 with SignomialsEnabled():
486 constraints = [A <= B.dot(C),
487 B <= 1,
488 C <= 1]
489 obj = 1/A[0] + 1/A[1]
490 m = Model(obj, constraints)
491 m.localsolve(verbosity=0, solver=self.solver)
493 def test_issue180(self):
494 L = Variable("L")
495 Lmax = Variable("L_{max}", 10)
496 W = Variable("W")
497 Wmax = Variable("W_{max}", 10)
498 A = Variable("A", 10)
499 Obj = Variable("Obj")
500 a_val = 0.01
501 a = Variable("a", a_val)
502 with SignomialsEnabled():
503 eqns = [L <= Lmax,
504 W <= Wmax,
505 L*W >= A,
506 Obj >= a*(2*L + 2*W) + (1-a)*(12 * W**-1 * L**-3)]
507 m = Model(Obj, eqns)
508 spsol = m.solve(self.solver, verbosity=0)
509 # now solve as GP
510 m[-1] = (Obj >= a_val*(2*L + 2*W) + (1-a_val)*(12 * W**-1 * L**-3))
511 del m.substitutions[m["a"]]
512 gpsol = m.solve(self.solver, verbosity=0)
513 self.assertAlmostEqual(spsol["cost"], gpsol["cost"])
515 def test_trivial_sp2(self):
516 x = Variable("x")
517 y = Variable("y")
519 # converging from above
520 with SignomialsEnabled():
521 constraints = [y + x >= 2, y >= x]
522 objective = y
523 x0 = 1
524 y0 = 2
525 m = Model(objective, constraints)
526 sol1 = m.localsolve(x0={x: x0, y: y0}, verbosity=0, solver=self.solver)
528 # converging from right
529 with SignomialsEnabled():
530 constraints = [y + x >= 2, y <= x]
531 objective = x
532 x0 = 2
533 y0 = 1
534 m = Model(objective, constraints)
535 sol2 = m.localsolve(x0={x: x0, y: y0}, verbosity=0, solver=self.solver)
537 self.assertAlmostEqual(sol1["variables"]["x"],
538 sol2["variables"]["x"], self.ndig)
539 self.assertAlmostEqual(sol1["variables"]["y"],
540 sol2["variables"]["x"], self.ndig)
542 def test_sp_initial_guess_sub(self):
543 x = Variable("x")
544 y = Variable("y")
545 x0 = 3
546 y0 = 2
547 with SignomialsEnabled():
548 constraints = [y + x >= 4, y <= x]
549 objective = x
550 m = Model(objective, constraints)
551 # Call to local solve with only variables
552 sol = m.localsolve(x0={"x": x0, y: y0}, verbosity=0,
553 solver=self.solver)
554 self.assertAlmostEqual(sol(x), 2, self.ndig)
555 self.assertAlmostEqual(sol["cost"], 2, self.ndig)
557 # Call to local solve with only variable strings
558 sol = m.localsolve(x0={"x": x0, "y": y0}, verbosity=0,
559 solver=self.solver)
560 self.assertAlmostEqual(sol("x"), 2, self.ndig)
561 self.assertAlmostEqual(sol["cost"], 2, self.ndig)
563 # Call to local solve with a mix of variable strings and variables
564 sol = m.localsolve(x0={"x": x0, y: y0}, verbosity=0,
565 solver=self.solver)
566 self.assertAlmostEqual(sol["cost"], 2, self.ndig)
568 def test_small_named_signomial(self):
569 x = Variable("x")
570 z = Variable("z")
571 local_ndig = 4
572 nonzero_adder = 0.1
573 with SignomialsEnabled():
574 J = 0.01*(x - 1)**2 + nonzero_adder
575 with NamedVariables("SmallSignomial"):
576 m = Model(z, [z >= J])
577 sol = m.localsolve(verbosity=0, solver=self.solver)
578 self.assertAlmostEqual(sol["cost"], nonzero_adder, local_ndig)
579 self.assertAlmostEqual(sol("x"), 0.98725425, self.ndig)
581 def test_sigs_not_allowed_in_cost(self):
582 with SignomialsEnabled():
583 x = Variable("x")
584 y = Variable("y")
585 J = 0.01*((x - 1)**2 + (y - 1)**2) + (x*y - 1)**2
586 m = Model(J)
587 with self.assertRaises(InvalidPosynomial):
588 m.localsolve(verbosity=0, solver=self.solver)
590 def test_partial_sub_signomial(self):
591 "Test SP partial x0 initialization"
592 x = Variable("x")
593 y = Variable("y")
594 with SignomialsEnabled():
595 m = Model(x, [x + y >= 1, y <= 0.5])
596 gp = m.sp().gp(x0={x: 0.5}) # pylint: disable=no-member
597 first_gp_constr_posy_exp, = gp.hmaps[1] # first after cost
598 self.assertEqual(first_gp_constr_posy_exp[x.key], -1./3)
600 def test_becomes_signomial(self):
601 "Test that a GP does not become an SP after substitutions"
602 x = Variable("x")
603 c = Variable("c")
604 y = Variable("y")
605 m = Model(x, [y >= 1 + c*x, y <= 0.5], {c: -1})
606 with self.assertRaises(InvalidGPConstraint):
607 with SignomialsEnabled():
608 m.gp()
609 with self.assertRaises(UnnecessarySGP):
610 m.localsolve(solver=self.solver)
612 def test_reassigned_constant_cost(self):
613 # for issue 1131
614 x = Variable("x")
615 x_min = Variable("x_min", 1)
616 y = Variable("y")
617 with SignomialsEnabled():
618 m = Model(y, [y + 0.5 >= x, x >= x_min, 6 >= y])
619 m.localsolve(verbosity=0, solver=self.solver)
620 del m.substitutions[x_min]
621 m.cost = 1/x_min
622 self.assertNotIn(x_min, m.sp().gp().substitutions) # pylint: disable=no-member
624 def test_unbounded_debugging(self):
625 "Test nearly-dual-feasible problems"
626 x = Variable("x")
627 y = Variable("y")
628 m = Model(x*y, [x*y**1.01 >= 100])
629 with self.assertRaises((DualInfeasible, UnknownInfeasible)):
630 m.solve(self.solver, verbosity=0)
631 # test one-sided bound
632 m = Model(x*y, Bounded(m, lower=0.001))
633 sol = m.solve(self.solver, verbosity=0)
634 bounds = sol["boundedness"]
635 self.assertEqual(bounds["sensitive to lower bound of 0.001"],
636 set([x.key]))
637 # end test one-sided bound
638 m = Model(x*y, [x*y**1.01 >= 100])
639 m = Model(x*y, Bounded(m))
640 sol = m.solve(self.solver, verbosity=0)
641 bounds = sol["boundedness"]
642 # depends on solver, platform, whims of the numerical deities
643 if "sensitive to upper bound of 1e+30" in bounds: # pragma: no cover
644 self.assertIn(y.key, bounds["sensitive to upper bound of 1e+30"])
645 else: # pragma: no cover
646 self.assertIn(x.key, bounds["sensitive to lower bound of 1e-30"])
649class TestModelSolverSpecific(unittest.TestCase):
650 "test cases run only for specific solvers"
651 def test_cvxopt_kwargs(self): # pragma: no cover
652 if "cvxopt" not in settings["installed_solvers"]:
653 return
654 x = Variable("x")
655 m = Model(x, [x >= 12])
656 # make sure it"s possible to pass the kktsolver option to cvxopt
657 sol = m.solve(solver="cvxopt", verbosity=0, kktsolver="ldl")
658 self.assertAlmostEqual(sol["cost"], 12., NDIGS["cvxopt"])
661class Thing(Model):
662 "a thing, for model testing"
663 def setup(self, length):
664 a = self.a = VectorVariable(length, "a", "g/m")
665 b = self.b = VectorVariable(length, "b", "m")
666 c = Variable("c", 17/4., "g")
667 return [a >= c/b]
670class Thing2(Model):
671 "another thing for model testing"
672 def setup(self):
673 return [Thing(2), Model()]
676class Box(Model):
677 """simple box for model testing
679 Variables
680 ---------
681 h [m] height
682 w [m] width
683 d [m] depth
684 V [m**3] volume
686 Upper Unbounded
687 ---------------
688 w, d, h
690 Lower Unbounded
691 ---------------
692 w, d, h
693 """
694 @parse_variables(__doc__, globals())
695 def setup(self):
696 return [V == h*w*d]
699class BoxAreaBounds(Model):
700 """for testing functionality of separate analysis models
702 Lower Unbounded
703 ---------------
704 h, d, w
705 """
706 def setup(self, box):
707 A_wall = Variable("A_{wall}", 100, "m^2", "Upper limit, wall area")
708 A_floor = Variable("A_{floor}", 50, "m^2", "Upper limit, floor area")
710 self.h, self.d, self.w = box.h, box.d, box.w
712 return [2*box.h*box.w + 2*box.h*box.d <= A_wall,
713 box.w*box.d <= A_floor]
716class Sub(Model):
717 "Submodel with mass, for testing"
718 def setup(self):
719 m = Variable("m", "lb", "mass")
722class Widget(Model):
723 "A model with two Sub models"
724 def setup(self):
725 m_tot = Variable("m_{tot}", "lb", "total mass")
726 self.subA = Sub()
727 self.subB = Sub()
728 return [self.subA, self.subB,
729 m_tot >= self.subA["m"] + self.subB["m"]]
732class TestModelNoSolve(unittest.TestCase):
733 "model tests that don't require a solver"
734 def test_modelname_added(self):
735 t = Thing(2)
736 for vk in t.varkeys:
737 self.assertEqual(vk.lineage, (("Thing", 0),))
739 def test_modelcontainmentprinting(self):
740 t = Thing2()
741 self.assertEqual(t["c"].key.models, ("Thing2", "Thing"))
742 self.assertIsInstance(t.str_without(), str)
743 self.assertIsInstance(t.latex(), str)
745 def test_no_naming_on_var_access(self):
746 # make sure that analysis models don't add their names to
747 # variables looked up from other models
748 box = Box()
749 area_bounds = BoxAreaBounds(box)
750 M = Model(box["V"], [box, area_bounds])
751 for var in ("h", "w", "d"):
752 self.assertEqual(len(M.variables_byname(var)), 1)
754 def test_duplicate_submodel_varnames(self):
755 w = Widget()
756 # w has two Sub models, both with their own variable m
757 self.assertEqual(len(w.variables_byname("m")), 2)
758 # keys for both submodel m's should be in the parent model varkeys
759 self.assertIn(w.subA["m"].key, w.varkeys)
760 self.assertIn(w.subB["m"].key, w.varkeys)
761 # keys of w.variables_byname("m") should match m.varkeys
762 m_vbn_keys = [v.key for v in w.variables_byname("m")]
763 self.assertIn(w.subA["m"].key, m_vbn_keys)
764 self.assertIn(w.subB["m"].key, m_vbn_keys)
765 # dig a level deeper, into the keymap
766 self.assertEqual(len(w.varkeys.keymap["m"]), 2)
767 w2 = Widget()
770TESTS = [TestModelSolverSpecific, TestModelNoSolve]
771MULTI_SOLVER_TESTS = [TestGP, TestSP]
773for testcase in MULTI_SOLVER_TESTS:
774 for solver in settings["installed_solvers"]:
775 if solver:
776 test = type(str(testcase.__name__+"_"+solver),
777 (testcase,), {})
778 setattr(test, "solver", solver)
779 setattr(test, "ndig", NDIGS[solver])
780 TESTS.append(test)
782if __name__ == "__main__": # pragma: no cover
783 # pylint: disable=wrong-import-position
784 from gpkit.tests.helpers import run_tests
785 run_tests(TESTS, verbosity=0)