Coverage for gpkit/tests/t_nomials.py: 100%
292 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 16:49 -0500
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 16:49 -0500
1"""Tests for Monomial, Posynomial, and Signomial classes"""
2import sys
3import unittest
4import numpy as np
5from gpkit import Variable, Monomial, Posynomial, Signomial, SignomialsEnabled
6from gpkit import VectorVariable, NomialArray
7from gpkit.nomials import NomialMap
8from gpkit.exceptions import InvalidPosynomial
9import gpkit
12class TestMonomial(unittest.TestCase):
13 """TestCase for the Monomial class"""
15 def test_init(self):
16 "Test multiple ways to create a Monomial"
17 m = Monomial({"x": 2, "y": -1}, 5)
18 m2 = Monomial({"x": 2, "y": -1}, 5)
19 x, = m.varkeys["x"]
20 y, = m.varkeys["y"]
21 self.assertEqual(m.exp, {x: 2, y: -1})
22 self.assertEqual(m.c, 5)
23 self.assertEqual(m, m2)
25 # default c and a
26 v = Variable("x")
27 x, = v.varkeys["x"]
28 self.assertEqual(v.exp, {x: 1})
29 self.assertEqual(v.c, 1)
31 # single (string) var with non-default c
32 v = 0.1*Variable("tau")
33 tau, = v.varkeys["tau"] # pylint: disable=no-member
34 self.assertEqual(v.exp, {tau: 1}) # pylint: disable=no-member
35 self.assertEqual(v.c, .1) # pylint: disable=no-member
37 # variable names not compatible with python namespaces
38 crazy_varstr = "what the !!!/$**?"
39 m = Monomial({"x": 1, crazy_varstr: .5}, 25)
40 crazy_varkey, = m.varkeys[crazy_varstr]
41 self.assertTrue(crazy_varkey in m.exp)
43 # non-positive c raises
44 self.assertRaises(InvalidPosynomial, Monomial, -2)
45 self.assertRaises(InvalidPosynomial, Monomial, -1)
46 self.assertRaises(InvalidPosynomial, Monomial, 0)
47 self.assertRaises(InvalidPosynomial, Monomial, 0.0)
49 # can create nameless Variables
50 x1 = Variable()
51 x2 = Variable()
52 V = Variable("V")
53 vel = Variable("V")
54 self.assertNotEqual(x1, x2)
55 self.assertEqual(V, vel)
57 # test label kwarg
58 x = Variable("x", label="dummy variable")
59 self.assertEqual(list(x.exp)[0].descr["label"], "dummy variable")
60 _ = hash(m)
61 _ = hash(x)
62 _ = hash(Monomial(x))
64 def test_repr(self):
65 "Simple tests for __repr__, which prints more than str"
66 x = Variable("x")
67 y = Variable("y")
68 m = 5*x**2/y
69 r = m.__repr__()
70 self.assertEqual(type(r), str)
71 if sys.platform[:3] != "win":
72 self.assertEqual(repr(m), "gpkit.Monomial(5·x²/y)")
74 def test_latex(self):
75 "Test latex string creation"
76 x = Variable("x")
77 m = Monomial({"x": 2, "y": -1}, 5).latex()
78 self.assertEqual(type(m), str)
79 self.assertEqual((5*x).latex(), "5x")
81 def test_str_with_units(self):
82 "Make sure __str__() works when units are involved"
83 S = Variable("S", units="m^2")
84 rho = Variable("rho", units="kg/m^3")
85 x = rho*S
86 xstr = x.str_without()
87 self.assertEqual(type(xstr), str)
88 self.assertTrue("S" in xstr and "rho" in xstr)
90 def test_add(self):
91 x = Variable("x")
92 y = Variable("y", units="ft")
93 with self.assertRaises(gpkit.DimensionalityError):
94 _ = x + y
96 def test_eq_ne(self):
97 "Test equality and inequality comparators"
98 # simple one
99 x = Variable("x")
100 y = Variable("y")
101 self.assertNotEqual(x, y)
102 self.assertFalse(x == y)
104 xx = Variable("x")
105 self.assertEqual(x, xx)
106 self.assertFalse(x != xx)
108 self.assertEqual(x, x)
109 self.assertFalse(x != x) # pylint: disable=comparison-with-itself
111 m = Monomial({}, 1)
112 self.assertEqual(m, 1)
113 self.assertEqual(m, Monomial({}))
115 # several vars
116 m1 = Monomial({"a": 3, "b": 2, "c": 1}, 5)
117 m2 = Monomial({"a": 3, "b": 2, "c": 1}, 5)
118 m3 = Monomial({"a": 3, "b": 2, "c": 1}, 6)
119 m4 = Monomial({"a": 3, "b": 2}, 5)
120 self.assertEqual(m1, m2)
121 self.assertNotEqual(m1, m3)
122 self.assertNotEqual(m1, m4)
124 # numeric
125 self.assertEqual(Monomial(3), 3)
126 self.assertEqual(Monomial(3), Monomial(3))
127 self.assertNotEqual(Monomial(3), 2)
128 self.assertNotEqual(Variable("x"), 3)
129 self.assertNotEqual(Monomial(3), Variable("x"))
131 def test_div(self):
132 "Test Monomial division"
133 x = Variable("x")
134 y = Variable("y")
135 z = Variable("z")
136 t = Variable("t")
137 a = 36*x/y
138 # sanity check
139 self.assertEqual(a, Monomial({"x": 1, "y": -1}, 36))
140 # divide by scalar
141 self.assertEqual(a/9, 4*x/y)
142 # divide by Monomial
143 b = a / z
144 self.assertEqual(b, 36*x/y/z)
145 # make sure x unchanged
146 self.assertEqual(a, Monomial({"x": 1, "y": -1}, 36))
147 # mixed new and old vars
148 c = a / (0.5*t**2/x)
149 self.assertEqual(c, Monomial({"x": 2, "y": -1, "t": -2}, 72))
151 def test_mul(self):
152 "Test monomial multiplication"
153 x = Monomial({"x": 1, "y": -1}, 4)
154 # test integer division
155 self.assertEqual(x/5, Monomial({"x": 1, "y": -1}, 0.8))
156 # divide by scalar
157 self.assertEqual(x*9, Monomial({"x": 1, "y": -1}, 36))
158 # divide by Monomial
159 y = x * Variable("z")
160 self.assertEqual(y, Monomial({"x": 1, "y": -1, "z": 1}, 4))
161 # make sure x unchanged
162 self.assertEqual(x, Monomial({"x": 1, "y": -1}, 4))
163 # mixed new and old vars
164 z = x * Monomial({"x": -1, "t": 2}, .5)
165 self.assertEqual(z, Monomial({"x": 0, "y": -1, "t": 2}, 2))
167 x0 = Variable("x0")
168 self.assertEqual(0.0, 0.0*x0)
169 x1 = Variable("x1")
170 n_hat = [1, 0]
171 p = n_hat[0]*x0 + n_hat[1]*x1
172 self.assertEqual(p, x0)
174 self.assertNotEqual((x+1), (x+1)*gpkit.units("m"))
176 def test_pow(self):
177 "Test Monomial exponentiation"
178 x = Monomial({"x": 1, "y": -1}, 4)
179 self.assertEqual(x, Monomial({"x": 1, "y": -1}, 4))
180 # identity
181 self.assertEqual(x/x, Monomial({}, 1))
182 # square
183 self.assertEqual(x*x, x**2)
184 # divide
185 y = Monomial({"x": 2, "y": 3}, 5)
186 self.assertEqual(x/y, x*y**-1)
187 # make sure x unchanged
188 self.assertEqual(x, Monomial({"x": 1, "y": -1}, 4))
190 def test_numerical_precision(self):
191 "not sure what to test here, placeholder for now"
192 c1, c2 = 1/700, 123e8
193 m1 = Monomial({"x": 2, "y": 1}, c1)
194 m2 = Monomial({"y": -1, "z": 3/2}, c2)
195 self.assertEqual(np.log((m1**4 * m2**3).c), # pylint: disable=no-member
196 4*np.log(c1) + 3*np.log(c2))
198 def test_units(self):
199 "make sure multiplication with units works (issue 492)"
200 # have had issues where Quantity.__mul__ causes wrong return type
201 m = 1.2 * gpkit.units.ft * Variable("x", "m")**2
202 self.assertTrue(isinstance(m, Monomial))
203 if m.units:
204 self.assertEqual(m.units, 1*gpkit.ureg.ft*gpkit.ureg.m**2)
205 # also multiply at the end, though this has not been a problem
206 m = 0.5 * Variable("x", "m")**2 * gpkit.units.kg
207 self.assertTrue(isinstance(m, Monomial))
208 if m.units:
209 self.assertEqual(m.units, 1*gpkit.ureg.kg*gpkit.ureg.m**2)
210 # and with vectors...
211 v = 0.5 * VectorVariable(3, "x", "m")**2 * gpkit.units.kg
212 self.assertTrue(isinstance(v, NomialArray))
213 self.assertEqual(v[0].units, 1*gpkit.ureg.kg*gpkit.ureg.m**2)
214 v = 0.5 * gpkit.units.kg * VectorVariable(3, "x", "m")**2
215 self.assertTrue(isinstance(v, NomialArray))
216 self.assertEqual(v[0].units, 1*gpkit.ureg.kg*gpkit.ureg.m**2)
219class TestSignomial(unittest.TestCase):
220 """TestCase for the Signomial class"""
222 def test_init(self):
223 "Test Signomial construction"
224 x = Variable("x")
225 y = Variable("y")
226 with SignomialsEnabled():
227 if sys.platform[:3] != "win":
228 self.assertEqual(str(1 - x - y**2 - 1), "1 - x - y² - 1")
229 self.assertEqual((1 - x/y**2).latex(), "-\\frac{x}{y^{2}} + 1")
230 _ = hash(1 - x/y**2)
231 self.assertRaises(TypeError, lambda: x-y)
233 def test_chop(self):
234 "Test Signomial deconstruction"
235 x = Variable("x")
236 y = Variable("y")
237 with SignomialsEnabled():
238 c = x + 5*y**2 - 0.2*x*y**0.78
239 monomials = c.chop()
240 with self.assertRaises(InvalidPosynomial):
241 c.chop()
242 with SignomialsEnabled():
243 self.assertIn(-0.2*x*y**0.78, monomials)
245 def test_mult(self):
246 "Test Signomial multiplication"
247 x = Variable("x")
248 with SignomialsEnabled():
249 self.assertEqual((x+1)*(x-1), x**2 - 1)
251 def test_eq_ne(self):
252 "Test Signomial equality and inequality operators"
253 x = Variable("x")
254 xu = Variable("x", units="ft")
255 with SignomialsEnabled():
256 self.assertEqual(x - x**2, -x**2 + x)
257 self.assertNotEqual(-x, -xu)
258 # numeric
259 self.assertEqual(Signomial(0), 0)
260 self.assertNotEqual(Signomial(0), 1)
261 self.assertEqual(Signomial(-3), -3)
262 self.assertNotEqual(Signomial(-3), 3)
265class TestPosynomial(unittest.TestCase):
266 """TestCase for the Posynomial class"""
268 def test_init(self): # pylint:disable=too-many-locals
269 "Test Posynomial construction"
270 x = Variable("x")
271 y = Variable("y")
272 ms = [Monomial({"x": 1, "y": 2}, 3.14),
273 0.5*Variable("y"),
274 Monomial({"x": 3, "y": 1}, 6),
275 Monomial(2)]
276 exps, cs = [], []
277 for m in ms:
278 cs += m.cs.tolist()
279 exps += m.exps
280 hmap = NomialMap(zip(exps, cs))
281 hmap.units_of_product(None)
282 p = Posynomial(hmap)
283 # check arithmetic
284 p2 = 3.14*x*y**2 + y/2 + x**3*6*y + 2
285 self.assertEqual(p, p2)
286 self.assertEqual(p, sum(ms))
287 _ = hash(p2)
289 exp1 = Monomial({"m": 1, "v": 2}).exp
290 exp2 = Monomial({"m": 1, "g": 1, "h": 1}).exp
291 hmap = NomialMap({exp1 : 0.5, exp2: 1})
292 hmap.units_of_product(None)
293 p = Posynomial(hmap)
294 m, = p.varkeys["m"]
295 g, = p.varkeys["g"]
296 h, = p.varkeys["h"]
297 v, = p.varkeys["v"]
298 self.assertTrue(all(isinstance(x, float) for x in p.cs))
299 self.assertEqual(len(p.exps), 2)
300 self.assertEqual(set(p.vks), set([m, g, h, v]))
302 def test_eq(self):
303 """Test Posynomial __eq__"""
304 x = Variable("x")
305 y = Variable("y")
306 self.assertTrue((1 + x) == (1 + x))
307 self.assertFalse((1 + x) == 2*(1 + x))
308 self.assertFalse((1 + x) == 0.5*(1 + x))
309 self.assertFalse((1 + x) == (1 + y))
310 x = Variable("x", value=3)
311 y = Variable("y", value=2)
312 self.assertEqual((1 + x**2).value, (4 + y + y**2).value)
314 def test_eq_units(self):
315 p1 = Variable("x") + Variable("y")
316 p2 = Variable("x") + Variable("y")
317 p1u = Variable("x", units="m") + Variable("y", units="m")
318 p2u = Variable("x", units="m") + Variable("y", units="m")
319 self.assertEqual(p1, p2)
320 self.assertEqual(p1u, p2u)
321 self.assertFalse(p1 == p1u)
322 self.assertNotEqual(p1, p1u)
324 def test_simplification(self):
325 "Make sure like monomial terms get automatically combined"
326 x = Variable("x")
327 y = Variable("y")
328 p1 = x + y + y + (x+y) + (y+x**2) + 3*x
329 p2 = 4*y + x**2 + 5*x
330 # ps1 = [list(exp.keys())for exp in p1.exps]
331 # ps2 = [list(exp.keys())for exp in p2.exps]
332 # print("%s, %s" % (ps1, ps2)) # python 3 dict reordering
333 self.assertEqual(p1, p2)
335 def test_posyposy_mult(self):
336 "Test multiplication of Posynomial with Posynomial"
337 x = Variable("x")
338 y = Variable("y")
339 p1 = x**2 + 2*y*x + y**2
340 p2 = (x+y)**2
341 # ps1 = [list(exp.keys())for exp in p1.exps]
342 # ps2 = [list(exp.keys())for exp in p2.exps]
343 # print("%s, %s" % (ps1, ps2)) # python 3 dict reordering
344 self.assertEqual(p1, p2)
345 p1 = (x+y)*(2*x+y**2)
346 p2 = 2*x**2 + 2*y*x + y**2*x + y**3
347 # ps1 = [list(exp.keys())for exp in p1.exps]
348 # ps2 = [list(exp.keys())for exp in p2.exps]
349 # print("%s, %s" % (ps1, ps2)) # python 3 dict reordering
350 self.assertEqual(p1, p2)
352 def test_constraint_gen(self):
353 "Test creation of Constraints via operator overloading"
354 x = Variable("x")
355 y = Variable("y")
356 p = x**2 + 2*y*x + y**2
357 self.assertEqual((p <= 1).as_hmapslt1({}), [p.hmap])
358 self.assertEqual((p <= x).as_hmapslt1({}), [(p/x).hmap])
360 def test_integer_division(self):
361 "Make sure division by integer doesn't use Python integer division"
362 x = Variable("x")
363 y = Variable("y")
364 p = 4*x + y
365 self.assertEqual(p/3, p/3)
366 equiv1 = all((p/3).cs == [1/3, 4/3])
367 equiv2 = all((p/3).cs == [4/3, 1/3])
368 self.assertTrue(equiv1 or equiv2)
370 def test_diff(self):
371 "Test differentiation (!!)"
372 x = Variable("x")
373 y = Variable("y")
374 self.assertEqual(x.diff(x), 1)
375 self.assertEqual(x.diff(y), 0)
376 self.assertEqual((y**2).diff(y), 2*y)
377 self.assertEqual((x + y**2).diff(y), 2*y)
378 self.assertEqual((x + y**2).diff(x.key), 1)
379 self.assertEqual((x + x*y**2).diff(y), 2*x*y) # pylint: disable=no-member
380 self.assertEqual((2*y).diff(y), 2) # pylint: disable=no-member
381 # test with units
382 x = Variable("x", units="ft")
383 d = (3*x**2).diff(x)
384 self.assertEqual(d, 6*x)
385 # test negative exponent
386 d = (1 + 1/y).diff(y)
387 with SignomialsEnabled():
388 expected = -y**-2
389 self.assertEqual(d, expected)
391 def test_mono_lower_bound(self):
392 "Test monomial approximation"
393 x = Variable("x")
394 y = Variable("y")
395 p = y**2 + 1
396 self.assertEqual(y.mono_lower_bound({y: 1}), y)
397 # pylint is confused because it thinks p is a Signomial
398 # pylint: disable=no-member
399 self.assertEqual(p.mono_lower_bound({y: 1}), 2*y)
400 self.assertEqual(p.mono_lower_bound({y: 0}), 1)
401 self.assertEqual((x*y**2 + 1).mono_lower_bound({y: 1, x: 1}),
402 2*y*x**0.5)
403 # test with units
404 d = Variable("d", units="ft")
405 h = Variable("h", units="ft")
406 p = (d*h**2 + h*d**2)
407 m = p.mono_lower_bound({d: 1, h: 1})
408 self.assertEqual(m, 2*(d*h)**1.5)
410# test substitution
412TESTS = [TestPosynomial, TestMonomial, TestSignomial]
414if __name__ == "__main__": # pragma: no cover
415 # pylint: disable=wrong-import-position
416 from gpkit.tests.helpers import run_tests
417 run_tests(TESTS)