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