Coverage for gpkit/tests/t_constraints.py: 100%
313 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-28 11:42 -0400
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-28 11:42 -0400
1"Unit tests for Constraint, MonomialEquality and SignomialInequality"
2import unittest
3import numpy as np
4from gpkit import Variable, SignomialsEnabled, Posynomial, VectorVariable
5from gpkit.nomials import SignomialInequality, PosynomialInequality
6from gpkit.nomials import MonomialEquality
7from gpkit import Model, ConstraintSet
8from gpkit.constraints.costed import CostedConstraintSet
9from gpkit.constraints.tight import Tight
10from gpkit.constraints.loose import Loose
11from gpkit.tests.helpers import run_tests
12from gpkit.exceptions import (InvalidGPConstraint, PrimalInfeasible,
13 DimensionalityError)
14from gpkit.constraints.relax import (ConstraintsRelaxed, ConstantsRelaxed,
15 ConstraintsRelaxedEqually)
16from gpkit.constraints.bounded import Bounded
17from gpkit.globals import NamedVariables
18import gpkit
21class TestConstraint(unittest.TestCase):
22 "Tests for Constraint class"
24 def test_uninited_element(self):
25 x = Variable("x")
27 class SelfPass(Model):
28 "A model which contains itself!"
29 def setup(self):
30 ConstraintSet([self, x <= 1])
32 self.assertRaises(ValueError, SelfPass)
34 def test_bad_elements(self):
35 x = Variable("x")
36 with self.assertRaises(ValueError):
37 _ = Model(x, [x == "A"])
38 with self.assertRaises(ValueError):
39 _ = Model(x, [x >= 1, x == "A"])
40 with self.assertRaises(ValueError):
41 _ = Model(x, [x >= 1, x == "A", x >= 1, ])
42 with self.assertRaises(ValueError):
43 _ = Model(x, [x == "A", x >= 1])
44 v = VectorVariable(2, "v")
45 with self.assertRaises(ValueError):
46 _ = Model(x, [v == "A"])
47 with self.assertRaises(TypeError):
48 _ = Model(x, [v <= ["A", "B"]])
49 with self.assertRaises(TypeError):
50 _ = Model(x, [v >= ["A", "B"]])
52 def test_evalfn(self):
53 x = Variable("x")
54 x2 = Variable("x^2", evalfn=lambda solv: solv[x]**2)
55 m = Model(x, [x >= 2])
56 m.unique_varkeys = set([x2.key])
57 sol = m.solve(verbosity=0)
58 self.assertAlmostEqual(sol(x2), sol(x)**2)
60 def test_relax_list(self):
61 x = Variable("x")
62 x_max = Variable("x_max", 1)
63 x_min = Variable("x_min", 2)
64 constraints = [x_min <= x, x <= x_max]
65 ConstraintsRelaxed(constraints)
66 ConstantsRelaxed(constraints)
67 ConstraintsRelaxedEqually(constraints)
69 def test_relax_linked(self):
70 x = Variable("x")
71 x_max = Variable("x_max", 1)
72 x_min = Variable("x_min", lambda c: 2*c[x_max])
73 zero = Variable("zero", lambda c: 0*c[x_max])
74 constraints = ConstraintSet([x_min + zero <= x, x + zero <= x_max])
75 _ = ConstantsRelaxed(constraints)
76 NamedVariables.reset_modelnumbers()
77 include_min = ConstantsRelaxed(constraints, include_only=["x_min"])
78 NamedVariables.reset_modelnumbers()
79 exclude_max = ConstantsRelaxed(constraints, exclude=["x_max"])
80 self.assertEqual(str(include_min), str(exclude_max))
82 def test_equality_relaxation(self):
83 x = Variable("x")
84 m = Model(x, [x == 3, x == 4])
85 rc = ConstraintsRelaxed(m)
86 m2 = Model(rc.relaxvars.prod() * x**0.01, rc)
87 self.assertAlmostEqual(m2.solve(verbosity=0)(x), 3, places=3)
89 def test_constraintget(self):
90 x = Variable("x")
91 x_ = Variable("x", lineage=[("_", 0)])
92 xv = VectorVariable(2, "x")
93 xv_ = VectorVariable(2, "x", lineage=[("_", 0)])
94 self.assertEqual(Model(x, [x >= 1])["x"], x)
95 with self.assertRaises(ValueError):
96 _ = Model(x, [x >= 1, x_ >= 1])["x"]
97 with self.assertRaises(ValueError):
98 _ = Model(x, [x >= 1, xv >= 1])["x"]
99 self.assertTrue(all(Model(xv.prod(), [xv >= 1])["x"] == xv))
100 with self.assertRaises(ValueError):
101 _ = Model(xv.prod(), [xv >= 1, xv_ >= 1])["x"]
102 with self.assertRaises(ValueError):
103 _ = Model(xv.prod(), [xv >= 1, x_ >= 1])["x"]
105 def test_additive_scalar(self):
106 "Make sure additive scalars simplify properly"
107 x = Variable('x')
108 c1 = 1 >= 10*x
109 c2 = 1 >= 5*x + 0.5
110 self.assertEqual(type(c1), PosynomialInequality)
111 self.assertEqual(type(c2), PosynomialInequality)
112 c1hmap, = c1.as_hmapslt1({})
113 c2hmap, = c2.as_hmapslt1({})
114 self.assertEqual(c1hmap, c2hmap)
116 def test_additive_scalar_gt1(self):
117 "1 can't be greater than (1 + something positive)"
118 x = Variable('x')
120 def constr():
121 "method that should raise a ValueError"
122 return 1 >= 5*x + 1.1
123 self.assertRaises(PrimalInfeasible, constr)
125 def test_init(self):
126 "Test Constraint __init__"
127 x = Variable('x')
128 y = Variable('y')
129 c = PosynomialInequality(x, ">=", y**2)
130 self.assertEqual(c.as_hmapslt1({}), [(y**2/x).hmap])
131 self.assertEqual(c.left, x)
132 self.assertEqual(c.right, y**2)
133 c = PosynomialInequality(x, "<=", y**2)
134 self.assertEqual(c.as_hmapslt1({}), [(x/y**2).hmap])
135 self.assertEqual(c.left, x)
136 self.assertEqual(c.right, y**2)
137 self.assertEqual(type((1 >= x).latex()), str)
139 def test_oper_overload(self):
140 "Test Constraint initialization by operator overloading"
141 x = Variable('x')
142 y = Variable('y')
143 c = (y >= 1 + x**2)
144 self.assertEqual(c.as_hmapslt1({}), [(1/y + x**2/y).hmap])
145 self.assertEqual(c.left, y)
146 self.assertEqual(c.right, 1 + x**2)
147 # same constraint, switched operator direction
148 c2 = (1 + x**2 <= y) # same as c
149 self.assertEqual(c2.as_hmapslt1({}), c.as_hmapslt1({}))
151 def test_sub_tol(self):
152 " Test PosyIneq feasibility tolerance under substitutions"
153 x = Variable('x')
154 y = Variable('y')
155 z = Variable('z')
156 PosynomialInequality.feastol = 1e-5
157 m = Model(z, [x == z, x >= y], {x: 1, y: 1.0001})
158 self.assertRaises(PrimalInfeasible, m.solve, verbosity=0)
159 PosynomialInequality.feastol = 1e-3
160 self.assertEqual(m.substitutions('x'), m.solve(verbosity=0)('x'))
162class TestCostedConstraint(unittest.TestCase):
163 "Tests for Costed Constraint class"
165 def test_vector_cost(self):
166 x = VectorVariable(2, "x")
167 self.assertRaises(ValueError, CostedConstraintSet, x, [])
168 _ = CostedConstraintSet(np.array(x[0]), [])
170class TestMonomialEquality(unittest.TestCase):
171 "Test monomial equality constraint class"
173 def test_init(self):
174 "Test initialization via both operator overloading and __init__"
175 x = Variable('x')
176 y = Variable('y')
177 mono = y**2/x
178 # operator overloading
179 mec = (x == y**2)
180 # __init__
181 mec2 = MonomialEquality(x, y**2)
182 self.assertTrue(mono.hmap in mec.as_hmapslt1({}))
183 self.assertTrue(mono.hmap in mec2.as_hmapslt1({}))
184 x = Variable("x", "ft")
185 y = Variable("y")
186 if gpkit.units:
187 self.assertRaises(DimensionalityError, MonomialEquality, x, y)
188 self.assertRaises(DimensionalityError, MonomialEquality, y, x)
190 def test_vector(self):
191 "Monomial Equalities with VectorVariables"
192 x = VectorVariable(3, "x")
193 self.assertFalse(x == 3)
194 self.assertTrue(x == x) # pylint: disable=comparison-with-itself
196 def test_inheritance(self):
197 "Make sure MonomialEquality inherits from the right things"
198 F = Variable('F')
199 m = Variable('m')
200 a = Variable('a')
201 mec = (F == m*a)
202 self.assertTrue(isinstance(mec, MonomialEquality))
204 def test_non_monomial(self):
205 "Try to initialize a MonomialEquality with non-monomial args"
206 x = Variable('x')
207 y = Variable('y')
209 def constr():
210 "method that should raise a TypeError"
211 MonomialEquality(x*y, x+y)
212 self.assertRaises(TypeError, constr)
214 def test_str(self):
215 "Test that MonomialEquality.__str__ returns a string"
216 x = Variable('x')
217 y = Variable('y')
218 mec = (x == y)
219 self.assertEqual(type(mec.str_without()), str)
221 def test_united_dimensionless(self):
222 "Check dimensionless unit-ed variables work"
223 x = Variable('x')
224 y = Variable('y', 'hr/day')
225 c = MonomialEquality(x, y)
226 self.assertTrue(isinstance(c, MonomialEquality))
229class TestSignomialInequality(unittest.TestCase):
230 "Test Signomial constraints"
232 def test_becomes_posy_sensitivities(self):
233 # pylint: disable=invalid-name
234 # model from #1165
235 ujet = Variable("ujet")
236 PK = Variable("PK")
237 Dp = Variable("Dp", 0.662)
238 fBLI = Variable("fBLI", 0.4)
239 fsurf = Variable("fsurf", 0.836)
240 mdot = Variable("mdot", 1/0.7376)
241 with SignomialsEnabled():
242 m = Model(PK, [mdot*ujet + fBLI*Dp >= 1,
243 PK >= 0.5*mdot*ujet*(2 + ujet) + fBLI*fsurf*Dp])
244 var_senss = m.solve(verbosity=0)["sensitivities"]["variables"]
245 self.assertAlmostEqual(var_senss[Dp], -0.16, 2)
246 self.assertAlmostEqual(var_senss[fBLI], -0.16, 2)
247 self.assertAlmostEqual(var_senss[fsurf], 0.19, 2)
248 self.assertAlmostEqual(var_senss[mdot], -0.17, 2)
250 # Linked variable
251 Dp = Variable("Dp", 0.662)
252 mDp = Variable("-Dp", lambda c: -c[Dp])
253 fBLI = Variable("fBLI", 0.4)
254 fsurf = Variable("fsurf", 0.836)
255 mdot = Variable("mdot", 1/0.7376)
256 m = Model(PK, [mdot*ujet >= 1 + fBLI*mDp,
257 PK >= 0.5*mdot*ujet*(2 + ujet) + fBLI*fsurf*Dp])
258 var_senss = m.solve(verbosity=0)["sensitivities"]["variables"]
259 self.assertAlmostEqual(var_senss[Dp], -0.16, 2)
260 self.assertAlmostEqual(var_senss[fBLI], -0.16, 2)
261 self.assertAlmostEqual(var_senss[fsurf], 0.19, 2)
262 self.assertAlmostEqual(var_senss[mdot], -0.17, 2)
264 # fixed negative variable
265 Dp = Variable("Dp", 0.662)
266 mDp = Variable("-Dp", -0.662)
267 fBLI = Variable("fBLI", 0.4)
268 fsurf = Variable("fsurf", 0.836)
269 mdot = Variable("mdot", 1/0.7376)
270 m = Model(PK, [mdot*ujet >= 1 + fBLI*mDp,
271 PK >= 0.5*mdot*ujet*(2 + ujet) + fBLI*fsurf*Dp])
272 var_senss = m.solve(verbosity=0)["sensitivities"]["variables"]
273 self.assertAlmostEqual(var_senss[Dp] + var_senss[mDp], -0.16, 2)
274 self.assertAlmostEqual(var_senss[fBLI], -0.16, 2)
275 self.assertAlmostEqual(var_senss[fsurf], 0.19, 2)
276 self.assertAlmostEqual(var_senss[mdot], -0.17, 2)
278 def test_init(self):
279 "Test initialization and types"
280 D = Variable('D', units="N")
281 x1, x2, x3 = (Variable("x_%s" % i, units="N") for i in range(3))
282 with self.assertRaises(TypeError):
283 sc = (D >= x1 + x2 - x3)
284 with SignomialsEnabled():
285 sc = (D >= x1 + x2 - x3)
286 self.assertTrue(isinstance(sc, SignomialInequality))
287 self.assertFalse(isinstance(sc, Posynomial))
289 def test_posyslt1(self):
290 x = Variable("x")
291 y = Variable("y")
292 with SignomialsEnabled():
293 sc = (x + y >= x*y)
294 # make sure that the error type doesn't change on our users
295 with self.assertRaises(InvalidGPConstraint):
296 _ = sc.as_hmapslt1({})
299class TestLoose(unittest.TestCase):
300 "Test loose constraint set"
302 def test_raiseerror(self):
303 x = Variable('x')
304 x_min = Variable('x_{min}', 2)
305 m = Model(x, [Loose([x >= x_min]),
306 x >= 1])
307 Loose.raiseerror = True
308 self.assertRaises(RuntimeWarning, m.solve, verbosity=0)
309 Loose.raiseerror = False
311 def test_posyconstr_in_gp(self):
312 "Tests loose constraint set with solve()"
313 x = Variable('x')
314 x_min = Variable('x_{min}', 2)
315 m = Model(x, [Loose([x >= x_min]),
316 x >= 1])
317 sol = m.solve(verbosity=0)
318 warndata = sol["warnings"]["Unexpectedly Tight Constraints"][0][1]
319 self.assertIs(warndata[-1], m[0][0])
320 self.assertAlmostEqual(warndata[0], +1, 3)
321 m.substitutions[x_min] = 0.5
322 self.assertAlmostEqual(m.solve(verbosity=0)["cost"], 1)
324 def test_posyconstr_in_sp(self):
325 x = Variable('x')
326 y = Variable('y')
327 x_min = Variable('x_min', 1)
328 y_min = Variable('y_min', 2)
329 with SignomialsEnabled():
330 sig_constraint = (x + y >= 3.5)
331 m = Model(x*y, [Loose([x >= y]),
332 x >= x_min, y >= y_min, sig_constraint])
333 sol = m.localsolve(verbosity=0)
334 warndata = sol["warnings"]["Unexpectedly Tight Constraints"][0][1]
335 self.assertIs(warndata[-1], m[0][0])
336 self.assertAlmostEqual(warndata[0], +1, 3)
337 m.substitutions[x_min] = 2
338 m.substitutions[y_min] = 1
339 self.assertAlmostEqual(m.localsolve(verbosity=0)["cost"], 2.5, 5)
342class TestTight(unittest.TestCase):
343 "Test tight constraint set"
345 def test_posyconstr_in_gp(self):
346 "Tests tight constraint set with solve()"
347 x = Variable('x')
348 x_min = Variable('x_{min}', 2)
349 m = Model(x, [Tight([x >= 1]),
350 x >= x_min])
351 sol = m.solve(verbosity=0)
352 warndata = sol["warnings"]["Unexpectedly Loose Constraints"][0][1]
353 self.assertIs(warndata[-1], m[0][0])
354 self.assertAlmostEqual(warndata[0], 1, 3)
355 m.substitutions[x_min] = 0.5
356 self.assertAlmostEqual(m.solve(verbosity=0)["cost"], 1)
358 def test_posyconstr_in_sp(self):
359 x = Variable('x')
360 y = Variable('y')
361 with SignomialsEnabled():
362 sig_constraint = (x + y >= 0.1)
363 m = Model(x*y, [Tight([x >= y]),
364 x >= 2, y >= 1, sig_constraint])
365 sol = m.localsolve(verbosity=0)
366 warndata = sol["warnings"]["Unexpectedly Loose Constraints"][0][1]
367 self.assertIs(warndata[-1], m[0][0])
368 self.assertAlmostEqual(warndata[0], 1, 3)
369 m.pop(1)
370 self.assertAlmostEqual(m.localsolve(verbosity=0)["cost"], 1, 5)
372 def test_sigconstr_in_sp(self):
373 "Tests tight constraint set with localsolve()"
374 x = Variable('x')
375 y = Variable('y')
376 x_min = Variable('x_{min}', 2)
377 y_max = Variable('y_{max}', 0.5)
378 with SignomialsEnabled():
379 m = Model(x, [Tight([x + y >= 1]),
380 x >= x_min,
381 y <= y_max])
382 sol = m.localsolve(verbosity=0)
383 warndata = sol["warnings"]["Unexpectedly Loose Constraints"][0][1]
384 self.assertIs(warndata[-1], m[0][0])
385 self.assertGreater(warndata[0], 0.5)
386 m.substitutions[x_min] = 0.5
387 self.assertAlmostEqual(m.localsolve(verbosity=0)["cost"], 0.5, 5)
390class TestBounded(unittest.TestCase):
391 "Test bounded constraint set"
393 def test_substitution_issue905(self):
394 x = Variable("x")
395 y = Variable("y")
396 m = Model(x, [x >= y], {"y": 1})
397 bm = Model(m.cost, Bounded(m))
398 sol = bm.solve(verbosity=0)
399 self.assertAlmostEqual(sol["cost"], 1.0)
400 bm = Model(m.cost, Bounded(m, lower=1e-10))
401 sol = bm.solve(verbosity=0)
402 self.assertAlmostEqual(sol["cost"], 1.0)
403 bm = Model(m.cost, Bounded(m, upper=1e10))
404 sol = bm.solve(verbosity=0)
405 self.assertAlmostEqual(sol["cost"], 1.0)
407TESTS = [TestConstraint, TestMonomialEquality, TestSignomialInequality,
408 TestTight, TestLoose, TestBounded, TestCostedConstraint]
410if __name__ == "__main__": # pragma: no cover
411 run_tests(TESTS)