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

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 

10 

11 

12class TestMonomial(unittest.TestCase): 

13 """TestCase for the Monomial class""" 

14 

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) 

24 

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) 

30 

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 

36 

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) 

42 

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) 

48 

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) 

56 

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)) 

63 

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)") 

73 

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") 

80 

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) 

89 

90 def test_add(self): 

91 x = Variable("x") 

92 y = Variable("y", units="ft") 

93 with self.assertRaises(gpkit.DimensionalityError): 

94 _ = x + y 

95 

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) 

103 

104 xx = Variable("x") 

105 self.assertEqual(x, xx) 

106 self.assertFalse(x != xx) 

107 

108 self.assertEqual(x, x) 

109 self.assertFalse(x != x) # pylint: disable=comparison-with-itself 

110 

111 m = Monomial({}, 1) 

112 self.assertEqual(m, 1) 

113 self.assertEqual(m, Monomial({})) 

114 

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) 

123 

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")) 

130 

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)) 

150 

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)) 

166 

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) 

173 

174 self.assertNotEqual((x+1), (x+1)*gpkit.units("m")) 

175 

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)) 

189 

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)) 

197 

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) 

217 

218 

219class TestSignomial(unittest.TestCase): 

220 """TestCase for the Signomial class""" 

221 

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) 

232 

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) 

244 

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) 

250 

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) 

263 

264 

265class TestPosynomial(unittest.TestCase): 

266 """TestCase for the Posynomial class""" 

267 

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) 

288 

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])) 

301 

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) 

313 

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) 

323 

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) 

334 

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) 

351 

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]) 

359 

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) 

369 

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) 

390 

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) 

409 

410# test substitution 

411 

412TESTS = [TestPosynomial, TestMonomial, TestSignomial] 

413 

414if __name__ == "__main__": # pragma: no cover 

415 # pylint: disable=wrong-import-position 

416 from gpkit.tests.helpers import run_tests 

417 run_tests(TESTS)