Coverage for gpkit/tests/t_constraints.py: 100%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

313 statements  

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 

19 

20 

21class TestConstraint(unittest.TestCase): 

22 "Tests for Constraint class" 

23 

24 def test_uninited_element(self): 

25 x = Variable("x") 

26 

27 class SelfPass(Model): 

28 "A model which contains itself!" 

29 def setup(self): 

30 ConstraintSet([self, x <= 1]) 

31 

32 self.assertRaises(ValueError, SelfPass) 

33 

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

51 

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) 

59 

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) 

68 

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

81 

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) 

88 

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

104 

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) 

115 

116 def test_additive_scalar_gt1(self): 

117 "1 can't be greater than (1 + something positive)" 

118 x = Variable('x') 

119 

120 def constr(): 

121 "method that should raise a ValueError" 

122 return 1 >= 5*x + 1.1 

123 self.assertRaises(PrimalInfeasible, constr) 

124 

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) 

138 

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({})) 

150 

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

161 

162class TestCostedConstraint(unittest.TestCase): 

163 "Tests for Costed Constraint class" 

164 

165 def test_vector_cost(self): 

166 x = VectorVariable(2, "x") 

167 self.assertRaises(ValueError, CostedConstraintSet, x, []) 

168 _ = CostedConstraintSet(np.array(x[0]), []) 

169 

170class TestMonomialEquality(unittest.TestCase): 

171 "Test monomial equality constraint class" 

172 

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) 

189 

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 

195 

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

203 

204 def test_non_monomial(self): 

205 "Try to initialize a MonomialEquality with non-monomial args" 

206 x = Variable('x') 

207 y = Variable('y') 

208 

209 def constr(): 

210 "method that should raise a TypeError" 

211 MonomialEquality(x*y, x+y) 

212 self.assertRaises(TypeError, constr) 

213 

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) 

220 

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

227 

228 

229class TestSignomialInequality(unittest.TestCase): 

230 "Test Signomial constraints" 

231 

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) 

249 

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) 

263 

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) 

277 

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

288 

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({}) 

297 

298 

299class TestLoose(unittest.TestCase): 

300 "Test loose constraint set" 

301 

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 

310 

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) 

323 

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) 

340 

341 

342class TestTight(unittest.TestCase): 

343 "Test tight constraint set" 

344 

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) 

357 

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) 

371 

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) 

388 

389 

390class TestBounded(unittest.TestCase): 

391 "Test bounded constraint set" 

392 

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) 

406 

407TESTS = [TestConstraint, TestMonomialEquality, TestSignomialInequality, 

408 TestTight, TestLoose, TestBounded, TestCostedConstraint] 

409 

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

411 run_tests(TESTS)