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

536 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-07 22:56 -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) 

17 

18 

19NDIGS = {"cvxopt": 5, "mosek_cli": 5, "mosek_conif": 3} 

20# name: decimal places of accuracy achieved in these tests 

21 

22# pylint: disable=invalid-name,attribute-defined-outside-init,unused-variable,undefined-variable,exec-used 

23 

24 

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 

34 

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) 

39 

40 def test_trivial_gp(self): 

41 """ 

42 Create and solve a trivial GP: 

43 minimize x + 2y 

44 subject to xy >= 1 

45 

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) 

62 

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) 

70 

71 # create a simple GP with equality constraints 

72 const = [ 

73 z == b/a, 

74 z == d/c, 

75 ] 

76 

77 # simple cost 

78 cost = a + b + c + d 

79 

80 # create a model 

81 m = Model(cost, const) 

82 

83 # solve the first version of the model (solves successfully) 

84 m.solve(verbosity=0, solver=self.solver) 

85 

86 # add a redundant equality constraint 

87 m.extend([ 

88 z == b/a 

89 ]) 

90 

91 # solver will fail and attempt to debug 

92 m.solve(verbosity=0, solver=self.solver) 

93 

94 

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) 

124 

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) 

135 

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) 

158 

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

167 

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) 

179 

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) 

193 

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

200 

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) 

209 

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

234 

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

243 

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

253 

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) 

265 

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) 

273 

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) 

281 

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) 

288 

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) 

295 

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) 

308 

309 

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 

315 

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) 

347 

348 def test_sp_bounded(self): 

349 x = Variable("x") 

350 y = Variable("y") 

351 

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) 

356 

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) 

364 

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

374 

375 def test_values_vs_subs(self): 

376 # Substitutions update method 

377 x = Variable("x") 

378 y = Variable("y") 

379 z = Variable("z") 

380 

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) 

388 

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) 

397 

398 def test_initially_infeasible(self): 

399 x = Variable("x") 

400 y = Variable("y") 

401 

402 with SignomialsEnabled(): 

403 sigc = x >= y + y**2 - y**3 

404 sigc2 = x <= y**0.5 

405 

406 m = Model(1/x, [sigc, sigc2, y <= 0.5]) 

407 

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) 

411 

412 def test_sp_substitutions(self): 

413 x = Variable("x") 

414 y = Variable("y", 1) 

415 z = Variable("z", 4) 

416 

417 old_stdout = sys.stdout 

418 sys.stdout = stringout = StringIO() 

419 

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) 

426 

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) 

433 

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

440 

441 def test_tautological(self): 

442 x = Variable("x") 

443 y = Variable("y") 

444 z = Variable("z") 

445 

446 old_stdout = sys.stdout 

447 sys.stdout = stringout = StringIO() 

448 

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

456 

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

463 

464 def test_impossible(self): 

465 x = Variable("x") 

466 y = Variable("y") 

467 z = Variable("z") 

468 

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) 

474 

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) 

488 

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) 

499 

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) 

508 

509 # issue #257 

510 

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) 

521 

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

543 

544 def test_trivial_sp2(self): 

545 x = Variable("x") 

546 y = Variable("y") 

547 

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) 

556 

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) 

565 

566 self.assertAlmostEqual(sol1["variables"]["x"], 

567 sol2["variables"]["x"], self.ndig) 

568 self.assertAlmostEqual(sol1["variables"]["y"], 

569 sol2["variables"]["x"], self.ndig) 

570 

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) 

585 

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) 

591 

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) 

596 

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) 

609 

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) 

618 

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) 

628 

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) 

640 

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 

652 

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

676 

677 

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

688 

689 

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] 

697 

698 

699class Thing2(Model): 

700 "another thing for model testing" 

701 def setup(self): 

702 return [Thing(2), Model()] 

703 

704 

705class Box(Model): 

706 """simple box for model testing 

707 

708 Variables 

709 --------- 

710 h [m] height 

711 w [m] width 

712 d [m] depth 

713 V [m**3] volume 

714 

715 Upper Unbounded 

716 --------------- 

717 w, d, h 

718 

719 Lower Unbounded 

720 --------------- 

721 w, d, h 

722 """ 

723 @parse_variables(__doc__, globals()) 

724 def setup(self): 

725 return [V == h*w*d] 

726 

727 

728class BoxAreaBounds(Model): 

729 """for testing functionality of separate analysis models 

730 

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

738 

739 self.h, self.d, self.w = box.h, box.d, box.w 

740 

741 return [2*box.h*box.w + 2*box.h*box.d <= A_wall, 

742 box.w*box.d <= A_floor] 

743 

744 

745class Sub(Model): 

746 "Submodel with mass, for testing" 

747 def setup(self): 

748 m = Variable("m", "lb", "mass") 

749 

750 

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

759 

760 

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

767 

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) 

773 

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) 

782 

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

797 

798 

799TESTS = [TestModelSolverSpecific, TestModelNoSolve] 

800MULTI_SOLVER_TESTS = [TestGP, TestSP] 

801 

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) 

810 

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)