Coverage for gpkit\tests\t_model.py: 0%

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

537 statements  

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_dup_eq_constraint(self): 

63 # from https://github.com/convexengineering/gpkit/issues/1551 

64 a = Variable("a", 1) 

65 b = Variable("b") 

66 c = Variable("c", 2) 

67 d = Variable("d") 

68 z = Variable("z", 0.5) 

69 

70 # create a simple GP with equality constraints 

71 const = [ 

72 z == b/a, 

73 z == d/c, 

74 ] 

75 

76 # simple cost 

77 cost = a + b + c + d 

78 

79 # create a model 

80 m = Model(cost, const) 

81 

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

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

84 

85 # add a redundant equality constraint 

86 m.extend([ 

87 z == b/a 

88 ]) 

89 

90 # solver will fail and attempt to debug 

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

92 

93 

94 def test_sigeq(self): 

95 x = Variable("x") 

96 y = VectorVariable(1, "y") 

97 c = Variable("c") 

98 # test left vector input to sigeq 

99 with SignomialsEnabled(): 

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

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

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

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

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

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

106 # test right vector input to sigeq 

107 with SignomialsEnabled(): 

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

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

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

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

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

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

114 # test scalar input to sigeq 

115 z = Variable("z") 

116 with SignomialsEnabled(): 

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

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

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

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

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

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

123 

124 def test_601(self): 

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

126 x = Variable("x") 

127 y = Variable("y", 2) 

128 m = Model(x, 

129 [x >= 1, 

130 y == 2]) 

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

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

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

134 

135 def test_cost_freeing(self): 

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

137 x = Variable("x", 1) 

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

139 intermediary = Variable("intermediary") 

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

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

142 solver=self.solver, verbosity=0) 

143 x = Variable("x", 1) 

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

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

146 self.assertRaises(PrimalInfeasible, m.solve, 

147 solver=self.solver, verbosity=0) 

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

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

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

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

152 self.assertRaises(UnboundedGP, m.solve, 

153 solver=self.solver, verbosity=0) 

154 gp = m.gp(checkbounds=False) 

155 self.assertRaises(DualInfeasible, gp.solve, 

156 solver=self.solver, verbosity=0) 

157 

158 def test_simple_united_gp(self): 

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

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

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

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

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

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

165 M = VectorVariable(2, "M") 

166 

167 prob = Model(1/R, 

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

169 M <= 0.76]) 

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

171 almostequal = self.assertAlmostEqual 

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

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

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

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

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

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

178 

179 def test_trivial_vector_gp(self): 

180 "Create and solve a trivial GP with VectorVariables" 

181 x = VectorVariable(2, "x") 

182 y = VectorVariable(2, "y") 

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

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

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

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

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

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

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

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

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

192 

193 def test_sensitivities(self): 

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

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

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

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

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

199 

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

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

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

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

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

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

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

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

208 

209 def test_mdd_example(self): 

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

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

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

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

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

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

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

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

218 # pylint: disable=no-member 

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

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

221 col=[0, 0, 0], 

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

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

224 col=[0, 0], 

225 data=[-1, 1])) 

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

227 col=[0, 0, 0], 

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

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

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

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

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

233 

234 def test_additive_constants(self): 

235 x = Variable("x") 

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

237 m.solve(verbosity=0) 

238 # pylint: disable=no-member 

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

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

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

242 

243 def test_zeroing(self): 

244 L = Variable("L") 

245 k = Variable("k", 0) 

246 with SignomialsEnabled(): 

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

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

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

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

251 self.assertTrue(sol.almost_equal(sol)) 

252 

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

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

255 if self.solver == "cvxopt": 

256 # cvxopt can"t solve this problem 

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

258 return 

259 x = Variable("x") 

260 y = Variable("y") 

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

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

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

264 

265 def test_constants_in_objective_1(self): 

266 "Issue 296" 

267 x1 = Variable("x1") 

268 x2 = Variable("x2") 

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

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

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

272 

273 def test_constants_in_objective_2(self): 

274 "Issue 296" 

275 x1 = Variable("x1") 

276 x2 = Variable("x2") 

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

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

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

280 

281 def test_terminating_constant_(self): 

282 x = Variable("x") 

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

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

285 sol = prob.solve(verbosity=0) 

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

287 

288 def test_exps_is_tuple(self): 

289 "issue 407" 

290 x = Variable("x") 

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

292 m.solve(verbosity=0) 

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

294 

295 def test_posy_simplification(self): 

296 "issue 525" 

297 D = Variable("D") 

298 mi = Variable("m_i") 

299 V = Variable("V", 1) 

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

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

302 gp1 = m1.gp() 

303 gp2 = m2.gp() 

304 # pylint: disable=no-member 

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

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

307 

308 

309class TestSP(unittest.TestCase): 

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

311 name = "TestSP_" 

312 solver = None 

313 ndig = None 

314 

315 def test_sp_relaxation(self): 

316 w = Variable("w") 

317 x = Variable("x") 

318 y = Variable("y") 

319 z = Variable("z") 

320 with SignomialsEnabled(): 

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

322 r1 = ConstantsRelaxed(m) 

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

324 with self.assertRaises(ValueError): 

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

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

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

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

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

330 with SignomialsEnabled(): 

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

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

333 r2 = ConstraintsRelaxed(m) 

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

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

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

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

338 with SignomialsEnabled(): 

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

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

341 r3 = ConstraintsRelaxedEqually(m) 

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

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

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

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

346 

347 def test_sp_bounded(self): 

348 x = Variable("x") 

349 y = Variable("y") 

350 

351 with SignomialsEnabled(): 

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

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

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

355 

356 with SignomialsEnabled(): 

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

358 with self.assertRaises(UnboundedGP): 

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

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

361 self.assertRaises(DualInfeasible, gp.solve, 

362 solver=self.solver, verbosity=0) 

363 

364 with SignomialsEnabled(): 

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

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

367 boundedness = sol["boundedness"] 

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

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

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

371 else: # pragma: no cover 

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

373 

374 def test_values_vs_subs(self): 

375 # Substitutions update method 

376 x = Variable("x") 

377 y = Variable("y") 

378 z = Variable("z") 

379 

380 with SignomialsEnabled(): 

381 constraints = [x + y >= z, 

382 y >= x - 1] 

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

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

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

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

387 

388 # Constant variable declaration method 

389 z = Variable("z", 5) 

390 with SignomialsEnabled(): 

391 constraints = [x + y >= z, 

392 y >= x - 1] 

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

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

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

396 

397 def test_initially_infeasible(self): 

398 x = Variable("x") 

399 y = Variable("y") 

400 

401 with SignomialsEnabled(): 

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

403 sigc2 = x <= y**0.5 

404 

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

406 

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

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

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

410 

411 def test_sp_substitutions(self): 

412 x = Variable("x") 

413 y = Variable("y", 1) 

414 z = Variable("z", 4) 

415 

416 from io import StringIO 

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 "Warning: SignomialConstraint %s became the tautological" 

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

438 "Warning: SignomialConstraint %s became the tautological" 

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

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

441 

442 def test_tautological(self): 

443 x = Variable("x") 

444 y = Variable("y") 

445 z = Variable("z") 

446 

447 from io import StringIO 

448 old_stdout = sys.stdout 

449 sys.stdout = stringout = StringIO() 

450 

451 with SignomialsEnabled(): 

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

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

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

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

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

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

458 

459 sys.stdout = old_stdout 

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

461 "Warning: SignomialConstraint %s became the tautological" 

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

463 "Warning: SignomialConstraint %s became the tautological" 

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

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

466 

467 def test_impossible(self): 

468 x = Variable("x") 

469 y = Variable("y") 

470 z = Variable("z") 

471 

472 with SignomialsEnabled(): 

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

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

475 with self.assertRaises(ValueError): 

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

477 

478 def test_trivial_sp(self): 

479 x = Variable("x") 

480 y = Variable("y") 

481 with SignomialsEnabled(): 

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

483 with self.assertRaises(InvalidGPConstraint): 

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

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

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

487 with SignomialsEnabled(): 

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

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

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

491 

492 def test_tautological_spconstraint(self): 

493 x = Variable("x") 

494 y = Variable("y") 

495 z = Variable("z", 0) 

496 with SignomialsEnabled(): 

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

498 with self.assertRaises(InvalidGPConstraint): 

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

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

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

502 

503 def test_relaxation(self): 

504 x = Variable("x") 

505 y = Variable("y") 

506 with SignomialsEnabled(): 

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

508 objective = x 

509 m = Model(objective, constraints) 

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

511 

512 # issue #257 

513 

514 A = VectorVariable(2, "A") 

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

516 C = VectorVariable(2, "C") 

517 with SignomialsEnabled(): 

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

519 B <= 1, 

520 C <= 1] 

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

522 m = Model(obj, constraints) 

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

524 

525 def test_issue180(self): 

526 L = Variable("L") 

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

528 W = Variable("W") 

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

530 A = Variable("A", 10) 

531 Obj = Variable("Obj") 

532 a_val = 0.01 

533 a = Variable("a", a_val) 

534 with SignomialsEnabled(): 

535 eqns = [L <= Lmax, 

536 W <= Wmax, 

537 L*W >= A, 

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

539 m = Model(Obj, eqns) 

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

541 # now solve as GP 

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

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

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

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

546 

547 def test_trivial_sp2(self): 

548 x = Variable("x") 

549 y = Variable("y") 

550 

551 # converging from above 

552 with SignomialsEnabled(): 

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

554 objective = y 

555 x0 = 1 

556 y0 = 2 

557 m = Model(objective, constraints) 

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

559 

560 # converging from right 

561 with SignomialsEnabled(): 

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

563 objective = x 

564 x0 = 2 

565 y0 = 1 

566 m = Model(objective, constraints) 

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

568 

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

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

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

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

573 

574 def test_sp_initial_guess_sub(self): 

575 x = Variable("x") 

576 y = Variable("y") 

577 x0 = 3 

578 y0 = 2 

579 with SignomialsEnabled(): 

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

581 objective = x 

582 m = Model(objective, constraints) 

583 # Call to local solve with only variables 

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

585 solver=self.solver) 

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

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

588 

589 # Call to local solve with only variable strings 

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

591 solver=self.solver) 

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

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

594 

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

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

597 solver=self.solver) 

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

599 

600 def test_small_named_signomial(self): 

601 x = Variable("x") 

602 z = Variable("z") 

603 local_ndig = 4 

604 nonzero_adder = 0.1 

605 with SignomialsEnabled(): 

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

607 with NamedVariables("SmallSignomial"): 

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

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

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

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

612 

613 def test_sigs_not_allowed_in_cost(self): 

614 with SignomialsEnabled(): 

615 x = Variable("x") 

616 y = Variable("y") 

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

618 m = Model(J) 

619 with self.assertRaises(InvalidPosynomial): 

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

621 

622 def test_partial_sub_signomial(self): 

623 "Test SP partial x0 initialization" 

624 x = Variable("x") 

625 y = Variable("y") 

626 with SignomialsEnabled(): 

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

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

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

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

631 

632 def test_becomes_signomial(self): 

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

634 x = Variable("x") 

635 c = Variable("c") 

636 y = Variable("y") 

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

638 with self.assertRaises(InvalidGPConstraint): 

639 with SignomialsEnabled(): 

640 m.gp() 

641 with self.assertRaises(UnnecessarySGP): 

642 m.localsolve(solver=self.solver) 

643 

644 def test_reassigned_constant_cost(self): 

645 # for issue 1131 

646 x = Variable("x") 

647 x_min = Variable("x_min", 1) 

648 y = Variable("y") 

649 with SignomialsEnabled(): 

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

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

652 del m.substitutions[x_min] 

653 m.cost = 1/x_min 

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

655 

656 def test_unbounded_debugging(self): 

657 "Test nearly-dual-feasible problems" 

658 x = Variable("x") 

659 y = Variable("y") 

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

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

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

663 # test one-sided bound 

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

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

666 bounds = sol["boundedness"] 

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

668 set([x.key])) 

669 # end test one-sided bound 

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

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

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

673 bounds = sol["boundedness"] 

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

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

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

677 else: # pragma: no cover 

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

679 

680 

681class TestModelSolverSpecific(unittest.TestCase): 

682 "test cases run only for specific solvers" 

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

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

685 return 

686 x = Variable("x") 

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

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

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

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

691 

692 

693class Thing(Model): 

694 "a thing, for model testing" 

695 def setup(self, length): 

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

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

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

699 return [a >= c/b] 

700 

701 

702class Thing2(Model): 

703 "another thing for model testing" 

704 def setup(self): 

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

706 

707 

708class Box(Model): 

709 """simple box for model testing 

710 

711 Variables 

712 --------- 

713 h [m] height 

714 w [m] width 

715 d [m] depth 

716 V [m**3] volume 

717 

718 Upper Unbounded 

719 --------------- 

720 w, d, h 

721 

722 Lower Unbounded 

723 --------------- 

724 w, d, h 

725 """ 

726 @parse_variables(__doc__, globals()) 

727 def setup(self): 

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

729 

730 

731class BoxAreaBounds(Model): 

732 """for testing functionality of separate analysis models 

733 

734 Lower Unbounded 

735 --------------- 

736 h, d, w 

737 """ 

738 def setup(self, box): 

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

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

741 

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

743 

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

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

746 

747 

748class Sub(Model): 

749 "Submodel with mass, for testing" 

750 def setup(self): 

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

752 

753 

754class Widget(Model): 

755 "A model with two Sub models" 

756 def setup(self): 

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

758 self.subA = Sub() 

759 self.subB = Sub() 

760 return [self.subA, self.subB, 

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

762 

763 

764class TestModelNoSolve(unittest.TestCase): 

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

766 def test_modelname_added(self): 

767 t = Thing(2) 

768 for vk in t.vks: 

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

770 

771 def test_modelcontainmentprinting(self): 

772 t = Thing2() 

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

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

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

776 

777 def test_no_naming_on_var_access(self): 

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

779 # variables looked up from other models 

780 box = Box() 

781 area_bounds = BoxAreaBounds(box) 

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

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

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

785 

786 def test_duplicate_submodel_varnames(self): 

787 w = Widget() 

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

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

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

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

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

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

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

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

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

797 # dig a level deeper, into the keymap 

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

799 w2 = Widget() 

800 

801 

802TESTS = [TestModelSolverSpecific, TestModelNoSolve] 

803MULTI_SOLVER_TESTS = [TestGP, TestSP] 

804 

805for testcase in MULTI_SOLVER_TESTS: 

806 for solver in settings["installed_solvers"]: 

807 if solver: 

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

809 (testcase,), {}) 

810 setattr(test, "solver", solver) 

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

812 TESTS.append(test) 

813 

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

815 # pylint: disable=wrong-import-position 

816 from gpkit.tests.helpers import run_tests 

817 run_tests(TESTS, verbosity=0)