Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Tests for GP and SP classes""" 

2import unittest 

3import sys 

4import numpy as np 

5from gpkit import (Model, settings, VectorVariable, Variable, 

6 SignomialsEnabled, ArrayVariable, SignomialEquality) 

7from gpkit.constraints.bounded import Bounded 

8from gpkit.small_classes import CootMatrix 

9from gpkit import NamedVariables, units, parse_variables 

10from gpkit.constraints.relax import ConstraintsRelaxed 

11from gpkit.constraints.relax import ConstraintsRelaxedEqually 

12from gpkit.constraints.relax import ConstantsRelaxed 

13from gpkit.exceptions import (UnknownInfeasible, InvalidPosynomial, 

14 InvalidGPConstraint, UnnecessarySGP, 

15 PrimalInfeasible, DualInfeasible, UnboundedGP) 

16 

17 

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

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

20 

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

22 

23 

24class TestGP(unittest.TestCase): 

25 """ 

26 Test GeometricPrograms. 

27 This TestCase gets run once for each installed solver. 

28 """ 

29 name = "TestGP_" 

30 # solver and ndig get set in loop at bottom this file, a bit hacky 

31 solver = None 

32 ndig = None 

33 

34 def test_no_monomial_constraints(self): 

35 x = Variable("x") 

36 sol = Model(x, [x + 1/x <= 3]).solve(solver=self.solver, verbosity=0) 

37 self.assertAlmostEqual(sol["cost"], 0.381966, self.ndig) 

38 

39 def test_trivial_gp(self): 

40 """ 

41 Create and solve a trivial GP: 

42 minimize x + 2y 

43 subject to xy >= 1 

44 

45 The global optimum is (x, y) = (sqrt(2), 1/sqrt(2)). 

46 """ 

47 x = Variable("x") 

48 y = Variable("y") 

49 prob = Model(cost=(x + 2*y), 

50 constraints=[x*y >= 1]) 

51 sol = prob.solve(solver=self.solver, verbosity=0) 

52 self.assertEqual(type(prob.latex()), str) 

53 # pylint: disable=protected-access 

54 self.assertEqual(type(prob._repr_latex_()), str) 

55 self.assertAlmostEqual(sol("x"), np.sqrt(2.), self.ndig) 

56 self.assertAlmostEqual(sol("y"), 1/np.sqrt(2.), self.ndig) 

57 self.assertAlmostEqual(sol("x") + 2*sol("y"), 

58 2*np.sqrt(2), 

59 self.ndig) 

60 self.assertAlmostEqual(sol["cost"], 2*np.sqrt(2), self.ndig) 

61 

62 def test_sigeq(self): 

63 x = Variable("x") 

64 y = VectorVariable(1, "y") 

65 c = Variable("c") 

66 # test left vector input to sigeq 

67 with SignomialsEnabled(): 

68 m = Model(c, [c >= (x + 0.25)**2 + (y - 0.5)**2, 

69 SignomialEquality(x**2 + x, y)]) 

70 sol = m.localsolve(solver=self.solver, verbosity=0) 

71 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig) 

72 self.assertAlmostEqual(sol("y")[0], 0.1908254, self.ndig) 

73 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig) 

74 # test right vector input to sigeq 

75 with SignomialsEnabled(): 

76 m = Model(c, [c >= (x + 0.25)**2 + (y - 0.5)**2, 

77 SignomialEquality(y, x**2 + x)]) 

78 sol = m.localsolve(solver=self.solver, verbosity=0) 

79 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig) 

80 self.assertAlmostEqual(sol("y")[0], 0.1908254, self.ndig) 

81 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig) 

82 # test scalar input to sigeq 

83 z = Variable("z") 

84 with SignomialsEnabled(): 

85 m = Model(c, [c >= (x + 0.25)**2 + (z - 0.5)**2, 

86 SignomialEquality(x**2 + x, z)]) 

87 sol = m.localsolve(solver=self.solver, verbosity=0) 

88 self.assertAlmostEqual(sol("x"), 0.1639472, self.ndig) 

89 self.assertAlmostEqual(sol("z"), 0.1908254, self.ndig) 

90 self.assertAlmostEqual(sol("c"), 0.2669448, self.ndig) 

91 

92 def test_601(self): 

93 # tautological monomials should solve but not pass to the solver 

94 x = Variable("x") 

95 y = Variable("y", 2) 

96 m = Model(x, 

97 [x >= 1, 

98 y == 2]) 

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

100 self.assertEqual(len(list(m.as_hmapslt1({}))), 3) 

101 self.assertEqual(len(m.program.hmaps), 2) 

102 

103 def test_cost_freeing(self): 

104 "Test freeing a variable that's in the cost." 

105 x = Variable("x", 1) 

106 x_min = Variable("x_{min}", 2) 

107 intermediary = Variable("intermediary") 

108 m = Model(x, [x >= intermediary, intermediary >= x_min]) 

109 self.assertRaises((PrimalInfeasible, UnknownInfeasible), m.solve, 

110 solver=self.solver, verbosity=0) 

111 x = Variable("x", 1) 

112 x_min = Variable("x_{min}", 2) 

113 m = Model(x, [x >= x_min]) 

114 self.assertRaises(PrimalInfeasible, m.solve, 

115 solver=self.solver, verbosity=0) 

116 del m.substitutions[m["x"]] 

117 self.assertAlmostEqual(m.solve(solver=self.solver, 

118 verbosity=0)["cost"], 2) 

119 del m.substitutions[m["x_{min}"]] 

120 self.assertRaises(UnboundedGP, m.solve, 

121 solver=self.solver, verbosity=0) 

122 gp = m.gp(checkbounds=False) 

123 self.assertRaises(DualInfeasible, gp.solve, 

124 solver=self.solver, verbosity=0) 

125 

126 def test_simple_united_gp(self): 

127 R = Variable("R", "nautical_miles") 

128 a0 = Variable("a0", 340.29, "m/s") 

129 theta = Variable("\\theta", 0.7598) 

130 t = Variable("t", 10, "hr") 

131 T_loiter = Variable("T_{loiter}", 1, "hr") 

132 T_reserve = Variable("T_{reserve}", 45, "min") 

133 M = VectorVariable(2, "M") 

134 

135 prob = Model(1/R, 

136 [t >= sum(R/a0/M/theta**0.5) + T_loiter + T_reserve, 

137 M <= 0.76]) 

138 sol = prob.solve(solver=self.solver, verbosity=0) 

139 almostequal = self.assertAlmostEqual 

140 almostequal(0.000553226/sol["cost"], 1, self.ndig) 

141 almostequal(340.29/sol["constants"]["a0"], 1, self.ndig) 

142 almostequal(340.29/sol["variables"]["a0"], 1, self.ndig) 

143 almostequal(340.29*a0.units/sol("a0"), 1, self.ndig) 

144 almostequal(1807.58/sol["freevariables"]["R"], 1, self.ndig) 

145 almostequal(1807.58*R.units/sol("R"), 1, self.ndig) 

146 

147 def test_trivial_vector_gp(self): 

148 "Create and solve a trivial GP with VectorVariables" 

149 x = VectorVariable(2, "x") 

150 y = VectorVariable(2, "y") 

151 prob = Model(cost=(sum(x) + 2*sum(y)), 

152 constraints=[x*y >= 1]) 

153 sol = prob.solve(solver=self.solver, verbosity=0) 

154 self.assertEqual(sol("x").shape, (2,)) 

155 self.assertEqual(sol("y").shape, (2,)) 

156 for x, y in zip(sol("x"), sol("y")): 

157 self.assertAlmostEqual(x, np.sqrt(2.), self.ndig) 

158 self.assertAlmostEqual(y, 1/np.sqrt(2.), self.ndig) 

159 self.assertAlmostEqual(sol["cost"]/(4*np.sqrt(2)), 1., self.ndig) 

160 

161 def test_sensitivities(self): 

162 W_payload = Variable("W_{payload}", 175*(195 + 30), "lbf") 

163 f_oew = Variable("f_{oew}", 0.53, "-", "OEW/MTOW") 

164 fuel_per_nm = Variable("\\theta_{fuel}", 13.75, "lbf/nautical_mile") 

165 R = Variable("R", 3000, "nautical_miles", "range") 

166 mtow = Variable("MTOW", "lbf", "max take off weight") 

167 

168 m = Model(61.3e6*units.USD*(mtow/(1e5*units.lbf))**0.807, 

169 [mtow >= W_payload + f_oew*mtow + fuel_per_nm*R]) 

170 sol = m.solve(solver=self.solver, verbosity=0) 

171 senss = sol["sensitivities"]["variables"] 

172 self.assertAlmostEqual(senss[f_oew], 0.91, 2) 

173 self.assertAlmostEqual(senss[R], 0.41, 2) 

174 self.assertAlmostEqual(senss[fuel_per_nm], 0.41, 2) 

175 self.assertAlmostEqual(senss[W_payload], 0.39, 2) 

176 

177 def test_mdd_example(self): 

178 Cl = Variable("Cl", 0.5, "-", "Lift Coefficient") 

179 Mdd = Variable("Mdd", "-", "Drag Divergence Mach Number") 

180 m1 = Model(1/Mdd, [1 >= 5*Mdd + 0.5, Mdd >= 0.00001]) 

181 m2 = Model(1/Mdd, [1 >= 5*Mdd + 0.5]) 

182 m3 = Model(1/Mdd, [1 >= 5*Mdd + Cl, Mdd >= 0.00001]) 

183 sol1 = m1.solve(solver=self.solver, verbosity=0) 

184 sol2 = m2.solve(solver=self.solver, verbosity=0) 

185 sol3 = m3.solve(solver=self.solver, verbosity=0) 

186 # pylint: disable=no-member 

187 gp1, gp2, gp3 = [m.program for m in [m1, m2, m3]] 

188 self.assertEqual(gp1.A, CootMatrix(row=[0, 1, 2], 

189 col=[0, 0, 0], 

190 data=[-1, 1, -1])) 

191 self.assertEqual(gp2.A, CootMatrix(row=[0, 1], 

192 col=[0, 0], 

193 data=[-1, 1])) 

194 self.assertEqual(gp3.A, CootMatrix(row=[0, 1, 2], 

195 col=[0, 0, 0], 

196 data=[-1, 1, -1])) 

197 self.assertTrue((gp3.A.todense() == np.matrix([-1, 1, -1]).T).all()) 

198 self.assertAlmostEqual(sol1(Mdd), sol2(Mdd)) 

199 self.assertAlmostEqual(sol1(Mdd), sol3(Mdd)) 

200 self.assertAlmostEqual(sol2(Mdd), sol3(Mdd)) 

201 

202 def test_additive_constants(self): 

203 x = Variable("x") 

204 m = Model(1/x, [1 >= 5*x + 0.5, 1 >= 10*x]) 

205 m.solve(verbosity=0) 

206 # pylint: disable=no-member 

207 gp = m.program # created by solve() 

208 self.assertEqual(gp.cs[1], gp.cs[2]) 

209 self.assertEqual(gp.A.data[1], gp.A.data[2]) 

210 

211 def test_zeroing(self): 

212 L = Variable("L") 

213 k = Variable("k", 0) 

214 with SignomialsEnabled(): 

215 constr = [L-5*k <= 10] 

216 sol = Model(1/L, constr).solve(self.solver, verbosity=0) 

217 self.assertAlmostEqual(sol(L), 10, self.ndig) 

218 self.assertAlmostEqual(sol["cost"], 0.1, self.ndig) 

219 self.assertTrue(sol.almost_equal(sol)) 

220 

221 def test_singular(self): # pragma: no cover 

222 "Create and solve GP with a singular A matrix" 

223 if self.solver == "cvxopt": 

224 # cvxopt can"t solve this problem 

225 # (see https://github.com/cvxopt/cvxopt/issues/36) 

226 return 

227 x = Variable("x") 

228 y = Variable("y") 

229 m = Model(y*x, [y*x >= 12]) 

230 sol = m.solve(solver=self.solver, verbosity=0) 

231 self.assertAlmostEqual(sol["cost"], 12, self.ndig) 

232 

233 def test_constants_in_objective_1(self): 

234 "Issue 296" 

235 x1 = Variable("x1") 

236 x2 = Variable("x2") 

237 m = Model(1. + x1 + x2, [x1 >= 1., x2 >= 1.]) 

238 sol = m.solve(solver=self.solver, verbosity=0) 

239 self.assertAlmostEqual(sol["cost"], 3, self.ndig) 

240 

241 def test_constants_in_objective_2(self): 

242 "Issue 296" 

243 x1 = Variable("x1") 

244 x2 = Variable("x2") 

245 m = Model(x1**2 + 100 + 3*x2, [x1 >= 10., x2 >= 15.]) 

246 sol = m.solve(solver=self.solver, verbosity=0) 

247 self.assertAlmostEqual(sol["cost"]/245., 1, self.ndig) 

248 

249 def test_terminating_constant_(self): 

250 x = Variable("x") 

251 y = Variable("y", value=0.5) 

252 prob = Model(1/x, [x + y <= 4]) 

253 sol = prob.solve(verbosity=0) 

254 self.assertAlmostEqual(sol["cost"], 1/3.5, self.ndig) 

255 

256 def test_exps_is_tuple(self): 

257 "issue 407" 

258 x = Variable("x") 

259 m = Model(x, [x >= 1]) 

260 m.solve(verbosity=0) 

261 self.assertEqual(type(m.program.cost.exps), tuple) 

262 

263 def test_posy_simplification(self): 

264 "issue 525" 

265 D = Variable("D") 

266 mi = Variable("m_i") 

267 V = Variable("V", 1) 

268 m1 = Model(D + V, [V >= mi + 0.4, mi >= 0.1, D >= mi**2]) 

269 m2 = Model(D + 1, [1 >= mi + 0.4, mi >= 0.1, D >= mi**2]) 

270 gp1 = m1.gp() 

271 gp2 = m2.gp() 

272 # pylint: disable=no-member 

273 self.assertEqual(gp1.A, gp2.A) 

274 self.assertTrue(gp1.cs == gp2.cs) 

275 

276 

277class TestSP(unittest.TestCase): 

278 "test case for SP class -- gets run for each installed solver" 

279 name = "TestSP_" 

280 solver = None 

281 ndig = None 

282 

283 def test_sp_relaxation(self): 

284 w = Variable("w") 

285 x = Variable("x") 

286 y = Variable("y") 

287 z = Variable("z") 

288 with SignomialsEnabled(): 

289 m = Model(x, [x+y >= w, x+y <= z/2, y <= x, y >= 1], {z: 3, w: 3}) 

290 r1 = ConstantsRelaxed(m) 

291 self.assertEqual(len(r1.vks), 8) 

292 with self.assertRaises(ValueError): 

293 _ = Model(x*r1.relaxvars, r1) # no "prod" 

294 sp = Model(x*r1.relaxvars.prod()**10, r1).sp(use_pccp=False) 

295 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"] 

296 self.assertAlmostEqual(cost/1024, 1, self.ndig) 

297 m.debug(verbosity=0, solver=self.solver) 

298 with SignomialsEnabled(): 

299 m = Model(x, [x+y >= z, x+y <= z/2, y <= x, y >= 1], {z: 3}) 

300 m.debug(verbosity=0, solver=self.solver) 

301 r2 = ConstraintsRelaxed(m) 

302 self.assertEqual(len(r2.vks), 7) 

303 sp = Model(x*r2.relaxvars.prod()**10, r2).sp(use_pccp=False) 

304 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"] 

305 self.assertAlmostEqual(cost/1024, 1, self.ndig) 

306 with SignomialsEnabled(): 

307 m = Model(x, [x+y >= z, x+y <= z/2, y <= x, y >= 1], {z: 3}) 

308 m.debug(verbosity=0, solver=self.solver) 

309 r3 = ConstraintsRelaxedEqually(m) 

310 self.assertEqual(len(r3.vks), 4) 

311 sp = Model(x*r3.relaxvar**10, r3).sp(use_pccp=False) 

312 cost = sp.localsolve(verbosity=0, solver=self.solver)["cost"] 

313 self.assertAlmostEqual(cost/(32*0.8786796585), 1, self.ndig) 

314 

315 def test_sp_bounded(self): 

316 x = Variable("x") 

317 y = Variable("y") 

318 

319 with SignomialsEnabled(): 

320 m = Model(x, [x + y >= 1, y <= 0.1]) # solves 

321 cost = m.localsolve(verbosity=0, solver=self.solver)["cost"] 

322 self.assertAlmostEqual(cost, 0.9, self.ndig) 

323 

324 with SignomialsEnabled(): 

325 m = Model(x, [x + y >= 1]) # dual infeasible 

326 with self.assertRaises(UnboundedGP): 

327 m.localsolve(verbosity=0, solver=self.solver) 

328 gp = m.sp(checkbounds=False).gp() 

329 self.assertRaises(DualInfeasible, gp.solve, 

330 solver=self.solver, verbosity=0) 

331 

332 with SignomialsEnabled(): 

333 m = Model(x, Bounded([x + y >= 1])) 

334 sol = m.localsolve(verbosity=0, solver=self.solver) 

335 boundedness = sol["boundedness"] 

336 # depends on solver, platform, whims of the numerical deities 

337 if "value near lower bound of 1e-30" in boundedness: # pragma: no cover 

338 self.assertIn(x.key, boundedness["value near lower bound of 1e-30"]) 

339 else: # pragma: no cover 

340 self.assertIn(y.key, boundedness["value near upper bound of 1e+30"]) 

341 

342 def test_values_vs_subs(self): 

343 # Substitutions update method 

344 x = Variable("x") 

345 y = Variable("y") 

346 z = Variable("z") 

347 

348 with SignomialsEnabled(): 

349 constraints = [x + y >= z, 

350 y >= x - 1] 

351 m = Model(x + y*z, constraints) 

352 m.substitutions.update({"z": 5}) 

353 sol = m.localsolve(verbosity=0, solver=self.solver) 

354 self.assertAlmostEqual(sol["cost"], 13, self.ndig) 

355 

356 # Constant variable declaration method 

357 z = Variable("z", 5) 

358 with SignomialsEnabled(): 

359 constraints = [x + y >= z, 

360 y >= x - 1] 

361 m = Model(x + y*z, constraints) 

362 sol = m.localsolve(verbosity=0, solver=self.solver) 

363 self.assertAlmostEqual(sol["cost"], 13, self.ndig) 

364 

365 def test_initially_infeasible(self): 

366 x = Variable("x") 

367 y = Variable("y") 

368 

369 with SignomialsEnabled(): 

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

371 sigc2 = x <= y**0.5 

372 

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

374 

375 sol = m.localsolve(verbosity=0, solver=self.solver) 

376 self.assertAlmostEqual(sol["cost"], 2**0.5, self.ndig) 

377 self.assertAlmostEqual(sol(y), 0.5, self.ndig) 

378 

379 def test_sp_substitutions(self): 

380 x = Variable("x") 

381 y = Variable("y", 1) 

382 z = Variable("z", 4) 

383 

384 from io import StringIO 

385 old_stdout = sys.stdout 

386 sys.stdout = stringout = StringIO() 

387 

388 with SignomialsEnabled(): 

389 m1 = Model(x, [x + z >= y]) 

390 with self.assertRaises(UnnecessarySGP): 

391 m1.localsolve(verbosity=0, solver=self.solver) 

392 with self.assertRaises(UnboundedGP): 

393 m1.solve(verbosity=0, solver=self.solver) 

394 

395 with SignomialsEnabled(): 

396 m2 = Model(x, [x + y >= z]) 

397 m2.substitutions[y] = 1 

398 m2.substitutions[z] = 4 

399 sol = m2.solve(self.solver, verbosity=0) 

400 self.assertAlmostEqual(sol["cost"], 3, self.ndig) 

401 

402 sys.stdout = old_stdout 

403 self.assertEqual(stringout.getvalue(), ( 

404 "Warning: SignomialConstraint %s became the tautological" 

405 " constraint 0 <= 3 + x after substitution.\n" 

406 "Warning: SignomialConstraint %s became the tautological" 

407 " constraint 0 <= 3 + x after substitution.\n" 

408 % (str(m1[0]), str(m1[0])))) 

409 

410 def test_tautological(self): 

411 x = Variable("x") 

412 y = Variable("y") 

413 z = Variable("z") 

414 

415 from io import StringIO 

416 old_stdout = sys.stdout 

417 sys.stdout = stringout = StringIO() 

418 

419 with SignomialsEnabled(): 

420 m1 = Model(x, [x + y >= z, x >= y]) 

421 m2 = Model(x, [x + 1 >= 0, x >= y]) 

422 m1.substitutions.update({"z": 0, "y": 1}) 

423 m2.substitutions.update({"y": 1}) 

424 self.assertAlmostEqual(m1.solve(self.solver, verbosity=0)["cost"], 

425 m2.solve(self.solver, verbosity=0)["cost"]) 

426 

427 sys.stdout = old_stdout 

428 self.assertEqual(stringout.getvalue(), ( 

429 "Warning: SignomialConstraint %s became the tautological" 

430 " constraint 0 <= 1 + x after substitution.\n" 

431 "Warning: SignomialConstraint %s became the tautological" 

432 " constraint 0 <= 1 + x after substitution.\n" 

433 % (str(m1[0]), str(m2[0])))) 

434 

435 def test_impossible(self): 

436 x = Variable("x") 

437 y = Variable("y") 

438 z = Variable("z") 

439 

440 with SignomialsEnabled(): 

441 m1 = Model(x, [x + y >= z, x >= y]) 

442 m1.substitutions.update({"x": 0, "y": 0}) 

443 with self.assertRaises(ValueError): 

444 _ = m1.localsolve(solver=self.solver) 

445 

446 def test_trivial_sp(self): 

447 x = Variable("x") 

448 y = Variable("y") 

449 with SignomialsEnabled(): 

450 m = Model(x, [x >= 1-y, y <= 0.1]) 

451 with self.assertRaises(InvalidGPConstraint): 

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

453 sol = m.localsolve(self.solver, verbosity=0) 

454 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig) 

455 with SignomialsEnabled(): 

456 m = Model(x, [x+y >= 1, y <= 0.1]) 

457 sol = m.localsolve(self.solver, verbosity=0) 

458 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig) 

459 

460 def test_tautological_spconstraint(self): 

461 x = Variable("x") 

462 y = Variable("y") 

463 z = Variable("z", 0) 

464 with SignomialsEnabled(): 

465 m = Model(x, [x >= 1-y, y <= 0.1, y >= z]) 

466 with self.assertRaises(InvalidGPConstraint): 

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

468 sol = m.localsolve(self.solver, verbosity=0) 

469 self.assertAlmostEqual(sol["variables"]["x"], 0.9, self.ndig) 

470 

471 def test_relaxation(self): 

472 x = Variable("x") 

473 y = Variable("y") 

474 with SignomialsEnabled(): 

475 constraints = [y + x >= 2, y <= x] 

476 objective = x 

477 m = Model(objective, constraints) 

478 m.localsolve(verbosity=0, solver=self.solver) 

479 

480 # issue #257 

481 

482 A = VectorVariable(2, "A") 

483 B = ArrayVariable([2, 2], "B") 

484 C = VectorVariable(2, "C") 

485 with SignomialsEnabled(): 

486 constraints = [A <= B.dot(C), 

487 B <= 1, 

488 C <= 1] 

489 obj = 1/A[0] + 1/A[1] 

490 m = Model(obj, constraints) 

491 m.localsolve(verbosity=0, solver=self.solver) 

492 

493 def test_issue180(self): 

494 L = Variable("L") 

495 Lmax = Variable("L_{max}", 10) 

496 W = Variable("W") 

497 Wmax = Variable("W_{max}", 10) 

498 A = Variable("A", 10) 

499 Obj = Variable("Obj") 

500 a_val = 0.01 

501 a = Variable("a", a_val) 

502 with SignomialsEnabled(): 

503 eqns = [L <= Lmax, 

504 W <= Wmax, 

505 L*W >= A, 

506 Obj >= a*(2*L + 2*W) + (1-a)*(12 * W**-1 * L**-3)] 

507 m = Model(Obj, eqns) 

508 spsol = m.solve(self.solver, verbosity=0) 

509 # now solve as GP 

510 m[-1] = (Obj >= a_val*(2*L + 2*W) + (1-a_val)*(12 * W**-1 * L**-3)) 

511 del m.substitutions[m["a"]] 

512 gpsol = m.solve(self.solver, verbosity=0) 

513 self.assertAlmostEqual(spsol["cost"], gpsol["cost"]) 

514 

515 def test_trivial_sp2(self): 

516 x = Variable("x") 

517 y = Variable("y") 

518 

519 # converging from above 

520 with SignomialsEnabled(): 

521 constraints = [y + x >= 2, y >= x] 

522 objective = y 

523 x0 = 1 

524 y0 = 2 

525 m = Model(objective, constraints) 

526 sol1 = m.localsolve(x0={x: x0, y: y0}, verbosity=0, solver=self.solver) 

527 

528 # converging from right 

529 with SignomialsEnabled(): 

530 constraints = [y + x >= 2, y <= x] 

531 objective = x 

532 x0 = 2 

533 y0 = 1 

534 m = Model(objective, constraints) 

535 sol2 = m.localsolve(x0={x: x0, y: y0}, verbosity=0, solver=self.solver) 

536 

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

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

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

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

541 

542 def test_sp_initial_guess_sub(self): 

543 x = Variable("x") 

544 y = Variable("y") 

545 x0 = 3 

546 y0 = 2 

547 with SignomialsEnabled(): 

548 constraints = [y + x >= 4, y <= x] 

549 objective = x 

550 m = Model(objective, constraints) 

551 # Call to local solve with only variables 

552 sol = m.localsolve(x0={"x": x0, y: y0}, verbosity=0, 

553 solver=self.solver) 

554 self.assertAlmostEqual(sol(x), 2, self.ndig) 

555 self.assertAlmostEqual(sol["cost"], 2, self.ndig) 

556 

557 # Call to local solve with only variable strings 

558 sol = m.localsolve(x0={"x": x0, "y": y0}, verbosity=0, 

559 solver=self.solver) 

560 self.assertAlmostEqual(sol("x"), 2, self.ndig) 

561 self.assertAlmostEqual(sol["cost"], 2, self.ndig) 

562 

563 # Call to local solve with a mix of variable strings and variables 

564 sol = m.localsolve(x0={"x": x0, y: y0}, verbosity=0, 

565 solver=self.solver) 

566 self.assertAlmostEqual(sol["cost"], 2, self.ndig) 

567 

568 def test_small_named_signomial(self): 

569 x = Variable("x") 

570 z = Variable("z") 

571 local_ndig = 4 

572 nonzero_adder = 0.1 

573 with SignomialsEnabled(): 

574 J = 0.01*(x - 1)**2 + nonzero_adder 

575 with NamedVariables("SmallSignomial"): 

576 m = Model(z, [z >= J]) 

577 sol = m.localsolve(verbosity=0, solver=self.solver) 

578 self.assertAlmostEqual(sol["cost"], nonzero_adder, local_ndig) 

579 self.assertAlmostEqual(sol("x"), 0.98725425, self.ndig) 

580 

581 def test_sigs_not_allowed_in_cost(self): 

582 with SignomialsEnabled(): 

583 x = Variable("x") 

584 y = Variable("y") 

585 J = 0.01*((x - 1)**2 + (y - 1)**2) + (x*y - 1)**2 

586 m = Model(J) 

587 with self.assertRaises(InvalidPosynomial): 

588 m.localsolve(verbosity=0, solver=self.solver) 

589 

590 def test_partial_sub_signomial(self): 

591 "Test SP partial x0 initialization" 

592 x = Variable("x") 

593 y = Variable("y") 

594 with SignomialsEnabled(): 

595 m = Model(x, [x + y >= 1, y <= 0.5]) 

596 gp = m.sp().gp(x0={x: 0.5}) # pylint: disable=no-member 

597 first_gp_constr_posy_exp, = gp.hmaps[1] # first after cost 

598 self.assertEqual(first_gp_constr_posy_exp[x.key], -1./3) 

599 

600 def test_becomes_signomial(self): 

601 "Test that a GP does not become an SP after substitutions" 

602 x = Variable("x") 

603 c = Variable("c") 

604 y = Variable("y") 

605 m = Model(x, [y >= 1 + c*x, y <= 0.5], {c: -1}) 

606 with self.assertRaises(InvalidGPConstraint): 

607 with SignomialsEnabled(): 

608 m.gp() 

609 with self.assertRaises(UnnecessarySGP): 

610 m.localsolve(solver=self.solver) 

611 

612 def test_reassigned_constant_cost(self): 

613 # for issue 1131 

614 x = Variable("x") 

615 x_min = Variable("x_min", 1) 

616 y = Variable("y") 

617 with SignomialsEnabled(): 

618 m = Model(y, [y + 0.5 >= x, x >= x_min, 6 >= y]) 

619 m.localsolve(verbosity=0, solver=self.solver) 

620 del m.substitutions[x_min] 

621 m.cost = 1/x_min 

622 self.assertNotIn(x_min, m.sp().gp().substitutions) # pylint: disable=no-member 

623 

624 def test_unbounded_debugging(self): 

625 "Test nearly-dual-feasible problems" 

626 x = Variable("x") 

627 y = Variable("y") 

628 m = Model(x*y, [x*y**1.01 >= 100]) 

629 with self.assertRaises((DualInfeasible, UnknownInfeasible)): 

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

631 # test one-sided bound 

632 m = Model(x*y, Bounded(m, lower=0.001)) 

633 sol = m.solve(self.solver, verbosity=0) 

634 bounds = sol["boundedness"] 

635 self.assertEqual(bounds["sensitive to lower bound of 0.001"], 

636 set([x.key])) 

637 # end test one-sided bound 

638 m = Model(x*y, [x*y**1.01 >= 100]) 

639 m = Model(x*y, Bounded(m)) 

640 sol = m.solve(self.solver, verbosity=0) 

641 bounds = sol["boundedness"] 

642 # depends on solver, platform, whims of the numerical deities 

643 if "sensitive to upper bound of 1e+30" in bounds: # pragma: no cover 

644 self.assertIn(y.key, bounds["sensitive to upper bound of 1e+30"]) 

645 else: # pragma: no cover 

646 self.assertIn(x.key, bounds["sensitive to lower bound of 1e-30"]) 

647 

648 

649class TestModelSolverSpecific(unittest.TestCase): 

650 "test cases run only for specific solvers" 

651 def test_cvxopt_kwargs(self): # pragma: no cover 

652 if "cvxopt" not in settings["installed_solvers"]: 

653 return 

654 x = Variable("x") 

655 m = Model(x, [x >= 12]) 

656 # make sure it"s possible to pass the kktsolver option to cvxopt 

657 sol = m.solve(solver="cvxopt", verbosity=0, kktsolver="ldl") 

658 self.assertAlmostEqual(sol["cost"], 12., NDIGS["cvxopt"]) 

659 

660 

661class Thing(Model): 

662 "a thing, for model testing" 

663 def setup(self, length): 

664 a = self.a = VectorVariable(length, "a", "g/m") 

665 b = self.b = VectorVariable(length, "b", "m") 

666 c = Variable("c", 17/4., "g") 

667 return [a >= c/b] 

668 

669 

670class Thing2(Model): 

671 "another thing for model testing" 

672 def setup(self): 

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

674 

675 

676class Box(Model): 

677 """simple box for model testing 

678 

679 Variables 

680 --------- 

681 h [m] height 

682 w [m] width 

683 d [m] depth 

684 V [m**3] volume 

685 

686 Upper Unbounded 

687 --------------- 

688 w, d, h 

689 

690 Lower Unbounded 

691 --------------- 

692 w, d, h 

693 """ 

694 @parse_variables(__doc__, globals()) 

695 def setup(self): 

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

697 

698 

699class BoxAreaBounds(Model): 

700 """for testing functionality of separate analysis models 

701 

702 Lower Unbounded 

703 --------------- 

704 h, d, w 

705 """ 

706 def setup(self, box): 

707 A_wall = Variable("A_{wall}", 100, "m^2", "Upper limit, wall area") 

708 A_floor = Variable("A_{floor}", 50, "m^2", "Upper limit, floor area") 

709 

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

711 

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

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

714 

715 

716class Sub(Model): 

717 "Submodel with mass, for testing" 

718 def setup(self): 

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

720 

721 

722class Widget(Model): 

723 "A model with two Sub models" 

724 def setup(self): 

725 m_tot = Variable("m_{tot}", "lb", "total mass") 

726 self.subA = Sub() 

727 self.subB = Sub() 

728 return [self.subA, self.subB, 

729 m_tot >= self.subA["m"] + self.subB["m"]] 

730 

731 

732class TestModelNoSolve(unittest.TestCase): 

733 "model tests that don't require a solver" 

734 def test_modelname_added(self): 

735 t = Thing(2) 

736 for vk in t.vks: 

737 self.assertEqual(vk.lineage, (("Thing", 0),)) 

738 

739 def test_modelcontainmentprinting(self): 

740 t = Thing2() 

741 self.assertEqual(t["c"].key.models, ("Thing2", "Thing")) 

742 self.assertIsInstance(t.str_without(), str) 

743 self.assertIsInstance(t.latex(), str) 

744 

745 def test_no_naming_on_var_access(self): 

746 # make sure that analysis models don't add their names to 

747 # variables looked up from other models 

748 box = Box() 

749 area_bounds = BoxAreaBounds(box) 

750 M = Model(box["V"], [box, area_bounds]) 

751 for var in ("h", "w", "d"): 

752 self.assertEqual(len(M.variables_byname(var)), 1) 

753 

754 def test_duplicate_submodel_varnames(self): 

755 w = Widget() 

756 # w has two Sub models, both with their own variable m 

757 self.assertEqual(len(w.variables_byname("m")), 2) 

758 # keys for both submodel m's should be in the parent model varkeys 

759 self.assertIn(w.subA["m"].key, w.varkeys) 

760 self.assertIn(w.subB["m"].key, w.varkeys) 

761 # keys of w.variables_byname("m") should match m.varkeys 

762 m_vbn_keys = [v.key for v in w.variables_byname("m")] 

763 self.assertIn(w.subA["m"].key, m_vbn_keys) 

764 self.assertIn(w.subB["m"].key, m_vbn_keys) 

765 # dig a level deeper, into the keymap 

766 self.assertEqual(len(w.varkeys.keymap["m"]), 2) 

767 w2 = Widget() 

768 

769 

770TESTS = [TestModelSolverSpecific, TestModelNoSolve] 

771MULTI_SOLVER_TESTS = [TestGP, TestSP] 

772 

773for testcase in MULTI_SOLVER_TESTS: 

774 for solver in settings["installed_solvers"]: 

775 if solver: 

776 test = type(str(testcase.__name__+"_"+solver), 

777 (testcase,), {}) 

778 setattr(test, "solver", solver) 

779 setattr(test, "ndig", NDIGS[solver]) 

780 TESTS.append(test) 

781 

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

783 # pylint: disable=wrong-import-position 

784 from gpkit.tests.helpers import run_tests 

785 run_tests(TESTS, verbosity=0)