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# pylint: skip-file 

2import string 

3from collections import defaultdict, namedtuple 

4from gpkit.nomials import Monomial, Posynomial 

5from gpkit.nomials.map import NomialMap 

6from gpkit.small_scripts import mag 

7from gpkit.small_classes import FixedScalar, HashVector 

8from gpkit.exceptions import DimensionalityError 

9from gpkit.repr_conventions import unitstr as get_unitstr 

10import numpy as np 

11 

12 

13Transform = namedtuple("Transform", ["factor", "power", "origkey"]) 

14Tree = namedtuple("Tree", ["key", "value", "branches"]) 

15 

16def get_free_vks(posy, solution): 

17 "Returns all free vks of a given posynomial for a given solution" 

18 return set(vk for vk in posy.vks if vk not in solution["constants"]) 

19 

20def get_model_breakdown(solution): 

21 breakdowns = {"|sensitivity|": 0} 

22 for modelname, senss in solution["sensitivities"]["models"].items(): 

23 senss = abs(senss) # for those monomial equalities 

24 *namespace, name = modelname.split(".") 

25 subbd = breakdowns 

26 subbd["|sensitivity|"] += senss 

27 for parent in namespace: 

28 if parent not in subbd: 

29 subbd[parent] = {parent: {}} 

30 subbd = subbd[parent] 

31 if "|sensitivity|" not in subbd: 

32 subbd["|sensitivity|"] = 0 

33 subbd["|sensitivity|"] += senss 

34 subbd[name] = {"|sensitivity|": senss} 

35 print(breakdowns["HyperloopSystem"]["|sensitivity|"]) 

36 breakdowns = {"|sensitivity|": 0} 

37 for constraint, senss in solution["sensitivities"]["constraints"].items(): 

38 senss = abs(senss) # for those monomial 

39 if senss <= 1e-5: 

40 continue 

41 subbd = breakdowns 

42 subbd["|sensitivity|"] += senss 

43 for parent in constraint.lineagestr().split("."): 

44 if parent not in subbd: 

45 subbd[parent] = {} 

46 subbd = subbd[parent] 

47 if "|sensitivity|" not in subbd: 

48 subbd["|sensitivity|"] = 0 

49 subbd["|sensitivity|"] += senss 

50 # treat vectors as namespace 

51 subbd[constraint] = {"|sensitivity|": senss} 

52 for vk, senss in solution["sensitivities"]["variables"].items(): 

53 senss = abs(senss) 

54 if hasattr(senss, "shape"): 

55 senss = np.nansum(senss) 

56 if senss <= 1e-5: 

57 continue 

58 subbd = breakdowns 

59 subbd["|sensitivity|"] += senss 

60 for parent in vk.lineagestr().split("."): 

61 if parent not in subbd: 

62 subbd[parent] = {} 

63 subbd = subbd[parent] 

64 if "|sensitivity|" not in subbd: 

65 subbd["|sensitivity|"] = 0 

66 subbd["|sensitivity|"] += senss 

67 # treat vectors as namespace (indexing vectors above) 

68 subbd[vk] = {"|sensitivity|": senss} 

69 print(breakdowns["|sensitivity|"]) 

70 return breakdowns 

71 

72def crawl_modelbd(bd, tree=None, name="Model"): 

73 if tree is None: 

74 tree = [] 

75 subtree = [] 

76 tree.append(Tree(name, bd.pop("|sensitivity|"), subtree)) 

77 for key, _ in sorted(bd.items(), key=lambda kv: kv[1]["|sensitivity|"], reverse=True): 

78 crawl_modelbd(bd[key], subtree, key) 

79 return tree[0] 

80 

81def get_breakdowns(solution): 

82 """Returns {key: (lt, gt, constraint)} for breakdown constrain in solution. 

83 

84 A breakdown constraint is any whose "gt" contains a single free variable. 

85 

86 (At present, monomial constraints check both sides as "gt") 

87 """ 

88 breakdowns = defaultdict(list) 

89 for constraint, senss in solution["sensitivities"]["constraints"].items(): 

90 if abs(senss) <= 1e-5: # only tight-ish ones 

91 continue 

92 if constraint.oper == ">=": 

93 gt, lt = (constraint.left, constraint.right) 

94 elif constraint.oper == "<=": 

95 lt, gt = (constraint.left, constraint.right) 

96 elif constraint.oper == "=": 

97 if senss > 0: # l_over_r is more sensitive - see nomials/math.py 

98 lt, gt = [constraint.left, constraint.right] 

99 else: # r_over_l is more sensitive - see nomials/math.py 

100 gt, lt = [constraint.left, constraint.right] 

101 if lt.any_nonpositive_cs: 

102 continue # no signomials 

103 freegt_vks = get_free_vks(gt, solution) 

104 if len(freegt_vks) != 1: 

105 continue # not a breakdown constraint 

106 brokendownvk, = freegt_vks 

107 if gt.exp[brokendownvk] < 0: 

108 if constraint.oper == "=" or len(lt.hmap) != 1: 

109 continue 

110 # We can try flipping gt/lt to make a breakdown. 

111 freelt_vks = get_free_vks(lt, solution) 

112 if len(lt.hmap) != 1 or len(freelt_vks) != 1: 

113 continue 

114 brokendownvk, = freelt_vks 

115 if lt.exp[brokendownvk] > 0: 

116 continue # not a breakdown constraint after transformation 

117 gt, lt = 1/lt, 1/gt 

118 breakdowns[brokendownvk].append((lt, gt, constraint)) 

119 breakdowns = dict(breakdowns) # remove the defaultdict-ness 

120 for key, bds in breakdowns.items(): 

121 # TODO: do multiple if sensitivities are quite close? right now we have to break ties! 

122 if len(bds) > 1: 

123 bds.sort(key=lambda lgc: (abs(solution["sensitivities"]["constraints"][lgc[2]]), str(lgc[0])), reverse=True) 

124 

125 prevlen = None 

126 while len(BASICALLY_FIXED_VARIABLES) != prevlen: 

127 prevlen = len(BASICALLY_FIXED_VARIABLES) 

128 for key in breakdowns: 

129 if key not in BASICALLY_FIXED_VARIABLES: 

130 get_fixity(key, breakdowns, solution, BASICALLY_FIXED_VARIABLES) 

131 return breakdowns 

132 

133BASICALLY_FIXED_VARIABLES = set() 

134 

135 

136def get_fixity(key, bd, solution, basically_fixed=set(), visited=set()): 

137 lt, gt, _ = bd[key][0] 

138 free_vks = get_free_vks(lt, solution).union(get_free_vks(gt, solution)) 

139 for vk in free_vks: 

140 if vk not in bd: 

141 return 

142 if vk in BASICALLY_FIXED_VARIABLES: 

143 continue 

144 if vk is key: 

145 continue 

146 if vk in visited: # been here before and it's not me 

147 return 

148 visited.add(key) 

149 get_fixity(vk, bd, solution, basically_fixed, visited) 

150 if vk not in BASICALLY_FIXED_VARIABLES: 

151 return 

152 basically_fixed.add(key) 

153 

154def crawl(key, bd, solution, basescale=1, permissivity=2, verbosity=0, 

155 visited_bdkeys=None): 

156 "Returns the tree of breakdowns of key in bd, sorting by solution's values" 

157 if key in bd: 

158 # TODO: do multiple if sensitivities are quite close? 

159 composition, keymon, constraint = bd[key][0] 

160 elif isinstance(key, Posynomial): 

161 composition = key 

162 keymon = None 

163 else: 

164 raise TypeError("the `key` argument must be a VarKey or Posynomial.") 

165 

166 if visited_bdkeys is None: 

167 visited_bdkeys = set() 

168 if verbosity: 

169 indent = verbosity-1 # HACK: a bit of overloading, here 

170 keyvalstr = "%s (%s)" % (key.str_without(["lineage", "units"]), 

171 get_valstr(key, solution)) 

172 print(" "*indent + keyvalstr + ", which breaks down further") 

173 indent += 1 

174 orig_subtree = subtree = [] 

175 tree = Tree(key, basescale, subtree) 

176 visited_bdkeys.add(key) 

177 if keymon is None: 

178 scale = solution(key)/basescale 

179 else: 

180 free_vks = get_free_vks(keymon, solution) 

181 subkey, = free_vks 

182 power = keymon.exp[subkey] 

183 fixed_vks = set(keymon.vks) - free_vks 

184 scale = solution(key)**power/basescale 

185 # TODO: make method that can handle both kinds of transforms 

186 if power != 1 or fixed_vks or mag(keymon.c) != 1 or keymon.units != key.units: 

187 units = 1 

188 exp = HashVector() 

189 for vk in free_vks: 

190 exp[vk] = keymon.exp[vk] 

191 if vk.units: 

192 units *= vk.units**keymon.exp[vk] 

193 subhmap = NomialMap({exp: 1}) 

194 subhmap.units = None if units == 1 else units 

195 freemon = Monomial(subhmap) 

196 factor = Monomial(keymon/freemon) 

197 scale = scale * solution(factor) 

198 if factor != 1: 

199 factor = factor**(-1/power) # invert the transform 

200 factor.ast = None 

201 if verbosity: 

202 keyvalstr = "%s (%s)" % (factor.str_without(["lineage", "units"]), 

203 get_valstr(factor, solution)) 

204 print(" "*indent + "(with a factor of " + keyvalstr + " )") 

205 subsubtree = [] 

206 transform = Transform(factor, 1, keymon) 

207 orig_subtree.append(Tree(transform, basescale, subsubtree)) 

208 orig_subtree = subsubtree 

209 if power != 1: 

210 if verbosity: 

211 print(" "*indent + "(with a power of %.2g )" % power) 

212 subsubtree = [] 

213 transform = Transform(1, 1/power, keymon) # inverted bc it's on the gt side 

214 orig_subtree.append(Tree(transform, basescale, subsubtree)) 

215 orig_subtree = subsubtree 

216 if verbosity: 

217 if keymon is not None: 

218 print(" "*indent + "in: " + constraint.str_without(["units", "lineage"])) 

219 print(" "*indent + "by:") 

220 indent += 1 

221 

222 # TODO: use ast_parsing instead of chop? 

223 monsols = [solution(mon) for mon in composition.chop()] 

224 parsed_monsols = [getattr(mon, "value", mon) for mon in monsols] 

225 monvals = [float(mon/scale) for mon in parsed_monsols] 

226 # sort by value, preserving order in case of value tie 

227 sortedmonvals = sorted(zip(monvals, range(len(monvals)), 

228 composition.chop()), reverse=True) 

229 for scaledmonval, _, mon in sortedmonvals: 

230 if not scaledmonval: 

231 continue 

232 scaledmonval = min(1, scaledmonval) # clip it 

233 subtree = orig_subtree # revert back to the original subtree 

234 

235 # time for some filtering 

236 free_vks = get_free_vks(mon, solution) 

237 basically_fixed_vks = {vk for vk in free_vks 

238 if vk in BASICALLY_FIXED_VARIABLES} 

239 if free_vks - basically_fixed_vks: # don't remove the last one 

240 free_vks = free_vks - basically_fixed_vks 

241 

242 if scaledmonval > 1 - permissivity: 

243 unbreakdownable_vks = {vk for vk in free_vks if vk not in bd} 

244 if free_vks - unbreakdownable_vks: # don't remove the last one 

245 free_vks = free_vks - unbreakdownable_vks 

246 

247 if len(free_vks) > 1 and permissivity > 1: 

248 best_vks = sorted((vk for vk in free_vks if vk in bd), 

249 key=lambda vk: 

250 (abs(solution["sensitivities"]["constraints"][bd[vk][0][2]]), 

251 str(bd[vk][0][0])), reverse=True) 

252 if best_vks: 

253 free_vks = set([best_vks[0]]) 

254 fixed_vks = mon.vks - free_vks 

255 

256 if len(free_vks) == 1: 

257 subkey, = free_vks 

258 power = mon.exp[subkey] 

259 if power != 1 and subkey not in bd: 

260 power = 1 # no need for a transform 

261 else: 

262 subkey = None 

263 power = 1 

264 if not free_vks: 

265 # prioritize showing some fixed_vks as if they were "free" 

266 if len(fixed_vks) == 1: 

267 free_vks = fixed_vks 

268 fixed_vks = set() 

269 else: 

270 for vk in list(fixed_vks): 

271 if vk.units and not vk.units.dimensionless: 

272 free_vks.add(vk) 

273 fixed_vks.remove(vk) 

274 

275 if free_vks and (fixed_vks or mag(mon.c) != 1): 

276 if subkey: 

277 kindafree_vks = set(vk for vk in fixed_vks 

278 if vk not in solution["constants"]) 

279 if kindafree_vks == fixed_vks: 

280 kindafree_vks = set() # don't remove ALL of them 

281 else: 

282 free_vks.update(kindafree_vks) 

283 units = 1 

284 exp = HashVector() 

285 for vk in free_vks: 

286 exp[vk] = mon.exp[vk] 

287 if vk.units: 

288 units *= vk.units**mon.exp[vk] 

289 subhmap = NomialMap({exp: 1}) 

290 subhmap.units = None if units is 1 else units 

291 freemon = Monomial(subhmap) 

292 factor = mon/freemon # autoconvert... 

293 if (factor.units is None and isinstance(factor, FixedScalar) 

294 and abs(factor.value - 1) <= 1e-4): 

295 factor = 1 # minor fudge to clear numerical inaccuracies 

296 if factor != 1 : 

297 factor.ast = None 

298 if verbosity: 

299 keyvalstr = "%s (%s)" % (factor.str_without(["lineage", "units"]), 

300 get_valstr(factor, solution)) 

301 print(" "*indent + "(with a factor of %s )" % keyvalstr) 

302 subsubtree = [] 

303 transform = Transform(factor, 1, mon) 

304 subtree.append(Tree(transform, scaledmonval, subsubtree)) 

305 subtree = subsubtree 

306 mon = freemon # simplifies units 

307 if subkey and fixed_vks and kindafree_vks: 

308 units = 1 

309 exp = HashVector() 

310 for vk in kindafree_vks: 

311 exp[vk] = mon.exp[vk] 

312 if vk.units: 

313 units *= vk.units**mon.exp[vk] 

314 subhmap = NomialMap({exp: 1}) 

315 subhmap.units = None if units == 1 else units 

316 factor = Monomial(subhmap) 

317 if factor != 1: 

318 factor.ast = None 

319 if verbosity: 

320 keyvalstr = "%s (%s)" % (factor.str_without(["lineage", "units"]), 

321 get_valstr(factor, solution)) 

322 print(" "*indent + "(with a factor of " + keyvalstr + " )") 

323 subsubtree = [] 

324 transform = Transform(factor, 1, mon) 

325 subtree.append(Tree(transform, scaledmonval, subsubtree)) 

326 subtree = subsubtree 

327 mon = mon/factor 

328 mon.ast = None 

329 if power != 1: 

330 if verbosity: 

331 print(" "*indent + "(with a power of %.2g )" % power) 

332 subsubtree = [] 

333 transform = Transform(1, power, mon) 

334 subtree.append(Tree(transform, scaledmonval, subsubtree)) 

335 subtree = subsubtree 

336 mon = mon**(1/power) 

337 mon.ast = None 

338 # TODO: make minscale an argument - currently an arbitrary 0.01 

339 if (subkey is not None and subkey not in visited_bdkeys 

340 and subkey in bd and scaledmonval > 0.01): 

341 if verbosity: 

342 verbosity = indent + 1 # slight hack 

343 try: 

344 subsubtree = crawl(subkey, bd, solution, scaledmonval, 

345 permissivity, verbosity, visited_bdkeys) 

346 subtree.append(subsubtree) 

347 continue 

348 except Exception as e: 

349 print(repr(e)) 

350 

351 if verbosity: 

352 keyvalstr = "%s (%s)" % (mon.str_without(["lineage", "units"]), 

353 get_valstr(mon, solution)) 

354 print(" "*indent + keyvalstr) 

355 subtree.append(Tree(mon, scaledmonval, [])) 

356 # clean up node combination - use a dictionary at the subtree level? 

357 ftidxs = defaultdict(list) 

358 for i, node in enumerate(orig_subtree): 

359 if isinstance(node.key, Transform): 

360 ftidxs[(node.key.factor, node.key.power)].append(i) 

361 to_delete = [] 

362 to_insert = [] 

363 for idxs in ftidxs.values(): 

364 if len(idxs) > 1: 

365 valsum = 0 

366 newbranches = [] 

367 new_origkeys = [] 

368 for idx in idxs: 

369 key, val, subbranches = orig_subtree[idx] 

370 valsum += val 

371 new_origkeys.append((key.origkey, val)) 

372 newbranches.extend(subbranches) 

373 to_delete.extend(idxs) 

374 newkey = Transform(key.factor, key.power, tuple(new_origkeys)) 

375 to_insert.append(Tree(newkey, valsum, newbranches)) 

376 for idx in sorted(to_delete, reverse=True): # high to low 

377 orig_subtree.pop(idx) 

378 if to_insert: 

379 orig_subtree.extend(to_insert) 

380 orig_subtree.sort(reverse=True, key=lambda branch: branch.value) 

381 return tree 

382 

383SYMBOLS = string.ascii_uppercase + string.ascii_lowercase 

384for ambiguous_symbol in "lILT": 

385 SYMBOLS = SYMBOLS.replace(ambiguous_symbol, "") 

386SYMBOLS += "⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵" 

387 

388def get_spanstr(legend, length, label, leftwards, solution): 

389 "Returns span visualization, collapsing labels to symbols" 

390 spacer, lend, rend = "│", "┯", "┷" 

391 if isinstance(label, Transform): 

392 spacer, lend, rend = "╎", "╤", "╧" 

393 if label.power != 1: 

394 spacer, lend, rend = "^", "^", "^" 

395 # remove origkey so they collide in the legends dictionary 

396 label = Transform(label.factor, label.power, None) 

397 # TODO: catch PI (or again could that come from AST parsing?) 

398 if label.power == 1 and len(str(label.factor)) == 1: 

399 legend[label] = str(label.factor) 

400 

401 if label not in legend: 

402 shortname = SYMBOLS[len(legend)] 

403 legend[label] = shortname 

404 else: 

405 shortname = legend[label] 

406 

407 if length <= 1: 

408 return shortname 

409 

410 shortside = int(max(0, length - 2)/2) 

411 longside = int(max(0, length - 3)/2) 

412 if leftwards: 

413 if length == 2: 

414 return lend + shortname 

415 return lend + spacer*shortside + shortname + spacer*longside + rend 

416 else: 

417 if length == 2: 

418 return shortname + rend 

419 # HACK: no corners on rightwards - only used for depth 0 

420 return "┃"*(longside+1) + shortname + "┃"*(shortside+1) 

421 

422def layer(map, tree, extent, maxdepth, depth=0): 

423 "Turns the tree into a 2D-array" 

424 key, val, branches = tree 

425 if len(map) <= depth: 

426 map.append([]) 

427 map[depth].append((key, extent)) 

428 if depth > maxdepth or not val: 

429 return map 

430 scale = extent/val 

431 

432 extents = [round(scale*node.value) for node in branches] 

433 for i, branch in enumerate(branches): 

434 k, v, bs = branch 

435 if isinstance(k, Transform): 

436 subscale = extents[i]/v 

437 if not any(round(subscale*subv) for _, subv, _ in bs): 

438 extents[i] = 0 # transform with no worthy heirs: misc it 

439 

440 if not any(extents) or (extent == 1 and not isinstance(key, Transform)): 

441 branches = [Tree(None, val, [])] # pad it out 

442 extents = [extent] 

443 elif not all(extents): # create a catch-all 

444 branches = branches.copy() 

445 surplus = extent - sum(extents) 

446 miscvkeys, miscval = [], 0 

447 for subextent in reversed(extents): 

448 if not subextent or (branches[-1].value < miscval and surplus < 0): 

449 k, v, _ = branches.pop() 

450 if isinstance(k, Transform): 

451 k = k.origkey # TODO: this is the only use of origkey - remove it 

452 if isinstance(k, tuple): 

453 vkeys = [(-kv[1], str(kv[0]), kv[0]) for kv in k] 

454 if not isinstance(k, tuple): 

455 vkeys = [(-v, str(k), k)] 

456 miscvkeys += vkeys 

457 surplus -= (round(scale*(miscval + v)) 

458 - round(scale*miscval) - subextent) 

459 miscval += v 

460 misckeys = tuple(k for _, _, k in sorted(miscvkeys)) 

461 branches.append(Tree(misckeys, miscval, [])) 

462 

463 extents = [int(round(scale*node.value)) for node in branches] 

464 surplus = extent - sum(extents) 

465 if surplus: 

466 sign = int(np.sign(surplus)) 

467 bump_priority = sorted((extent, sign*node.value, i) 

468 for i, (node, extent) 

469 in enumerate(zip(branches, extents))) 

470 while surplus: 

471 extents[bump_priority.pop()[-1]] += sign 

472 surplus -= sign 

473 if not isinstance(key, Transform): 

474 if len([ext for ext in extents if ext]) >= max(extent-1, 2): 

475 # if we'd branch a lot (all ones but one, or at all if extent <= 3) 

476 branches = [Tree(None, val, [])] 

477 extents = [extent] 

478 for branch, subextent in zip(branches, extents): 

479 if subextent: 

480 layer(map, branch, subextent, maxdepth, depth+1) 

481 return map 

482 

483def plumb(tree, depth=0): 

484 "Finds maximum depth of a tree" 

485 maxdepth = depth 

486 for branch in tree.branches: 

487 maxdepth = max(maxdepth, plumb(branch, depth+1)) 

488 return maxdepth 

489 

490def graph(tree, solution, extent=None, maxdepth=None, collapse=True): 

491 "Prints breakdown" 

492 if maxdepth is None: 

493 maxdepth = plumb(tree) - 1 

494 if extent is None: # autozoom in from 20 

495 prev_extent = None 

496 extent = 20 

497 while prev_extent != extent: 

498 mt = layer([], tree, extent, maxdepth) 

499 prev_extent = extent 

500 extent = min(extent, 4*max(len(at_depth) for at_depth in mt)) 

501 else: 

502 mt = layer([], tree, extent, maxdepth) 

503 scale = 1/extent 

504 legend = {} 

505 chararray = np.full((len(mt), extent), "", "object") 

506 for depth, elements_at_depth in enumerate(mt): 

507 row = "" 

508 for i, (element, length) in enumerate(elements_at_depth): 

509 if element is None: 

510 row += " "*length 

511 continue 

512 leftwards = depth > 0 and length > 2 

513 row += get_spanstr(legend, length, element, leftwards, solution) 

514 if row.strip(): 

515 chararray[depth, :] = list(row) 

516 

517 # Format depth=0 

518 A_key, = [key for key, value in legend.items() if value == "A"] 

519 A_str = get_keystr(A_key, solution) 

520 A_valstr = get_valstr(A_key, solution, into="(%s)") 

521 fmt = "{0:>%s}" % (max(len(A_str), len(A_valstr)) + 3) 

522 for j, entry in enumerate(chararray[0,:]): 

523 if entry == "A": 

524 chararray[0,j] = fmt.format(A_str + "╺┫") 

525 chararray[0,j+1] = fmt.format(A_valstr + " ┃") 

526 else: 

527 chararray[0,j] = fmt.format(entry) 

528 

529 # Format depths 1+ 

530 labeled = set() 

531 new_legend = {} 

532 for pos in range(extent): 

533 for depth in reversed(range(1,len(mt))): 

534 value = chararray[depth, pos] 

535 if value == " ": # spacer character 

536 chararray[depth, pos] = "" 

537 continue 

538 elif not value or value not in SYMBOLS: 

539 continue 

540 key, = [k for k, val in legend.items() if val == value] 

541 if getattr(key, "vks", None) and len(key.vks) == 1 and all(vk in new_legend for vk in key.vks): 

542 key, = key.vks 

543 if key not in new_legend and (isinstance(key, tuple) or (depth != len(mt) - 1 and chararray[depth+1, pos] != "")): 

544 new_legend[key] = SYMBOLS[len(new_legend)] 

545 if key in new_legend: 

546 chararray[depth, pos] = new_legend[key] 

547 if isinstance(key, tuple) and not isinstance(key, Transform): 

548 chararray[depth, pos] = "*" + chararray[depth, pos] 

549 if collapse: 

550 continue 

551 tryup, trydn = True, True 

552 span = 0 

553 keystr = get_keystr(key, solution) 

554 if not collapse: 

555 fmt = "{0:<%s}" % (len(keystr) + 3) 

556 else: 

557 fmt = "{0:<1}" 

558 while tryup or trydn: 

559 span += 1 

560 if tryup: 

561 if pos - span < 0: 

562 tryup = False 

563 else: 

564 upchar = chararray[depth, pos-span] 

565 if upchar == "│": 

566 chararray[depth, pos-span] = fmt.format("┃") 

567 elif upchar == "┯": 

568 chararray[depth, pos-span] = fmt.format("┓") 

569 else: 

570 tryup = False 

571 if trydn: 

572 if pos + span >= extent: 

573 trydn = False 

574 else: 

575 dnchar = chararray[depth, pos+span] 

576 if dnchar == "│": 

577 chararray[depth, pos+span] = fmt.format("┃") 

578 elif dnchar == "┷": 

579 chararray[depth, pos+span] = fmt.format("┛") 

580 else: 

581 trydn = False 

582 #TODO: make submodels show up with this; bd should be an argument 

583 if key in bd or (hasattr(key, "vks") and key.vks and any(vk in bd for vk in key.vks)): 

584 linkstr = "┣┉" 

585 else: 

586 linkstr = "┣╸" 

587 if not (isinstance(key, FixedScalar) or keystr in labeled): 

588 labeled.add(keystr) 

589 valuestr = get_valstr(key, solution, into=" (%s)") 

590 if span > 1 and (pos + 2 >= extent or chararray[depth, pos+1] == "┃"): 

591 chararray[depth, pos+1] += valuestr 

592 else: 

593 keystr += valuestr 

594 chararray[depth, pos] = linkstr + keystr 

595 

596 # Rotate and print 

597 vertstr = "\n".join(" " + "".join(row) for row in chararray.T.tolist()) 

598 print() 

599 print(vertstr) 

600 print() 

601 legend = new_legend 

602 

603 # Create and print legend 

604 if collapse: 

605 legend_lines = [] 

606 for key, shortname in sorted(legend.items(), key=lambda kv: kv[1]): 

607 legend_lines.append(legend_entry(key, shortname, solution)) 

608 maxlens = [max(len(el) for el in col) for col in zip(*legend_lines)] 

609 fmts = ["{0:<%s}" % L for L in maxlens] 

610 for line in legend_lines: 

611 line = "".join(fmt.format(cell) 

612 for fmt, cell in zip(fmts, line) if cell).rstrip() 

613 print(" " + line) 

614 

615def legend_entry(key, shortname, solution): 

616 "Returns list of legend elements" 

617 operator = note = "" 

618 keystr = valuestr = " " 

619 operator = "= " if shortname else " + " 

620 if isinstance(key, Transform): 

621 if key.power == 1: 

622 operator = " ×" 

623 key = key.factor 

624 free, quasifixed = False, False 

625 if any(vk not in BASICALLY_FIXED_VARIABLES 

626 for vk in get_free_vks(key, solution)): 

627 note = " [free factor]" 

628 else: 

629 valuestr = " ^%.3g" % key.power 

630 key = None 

631 if key is not None: 

632 if not isinstance(key, FixedScalar): 

633 keystr = get_keystr(key, solution) 

634 valuestr = get_valstr(key, solution, into=" "+operator+"%s") 

635 return ["%-4s" % shortname, keystr, valuestr, note] 

636 

637def get_keystr(key, solution): 

638 if key is None: 

639 out = " " 

640 elif key is solution.costposy: 

641 out = "Cost" 

642 elif hasattr(key, "str_without"): 

643 out = key.str_without(["lineage", "units"]) 

644 elif isinstance(key, tuple): 

645 return "(%i entries)" % len(key) 

646 else: 

647 out = str(key) 

648 # TODO: use fixedfactors to drop below 50 

649 if len(out) > 120: 

650 out = out[:120]+"…" 

651 return out 

652 

653def get_valstr(key, solution, into="%s"): 

654 "Returns formatted string of the value of key in solution." 

655 try: 

656 value = solution(key) 

657 except (ValueError, TypeError): 

658 try: 

659 value = sum(solution(subkey) for subkey in key) 

660 except (ValueError, TypeError): 

661 return " " 

662 if isinstance(value, FixedScalar): 

663 value = value.value 

664 if hasattr(key, "unitstr"): 

665 unitstr = key.unitstr() 

666 else: 

667 if hasattr(value, "units"): 

668 value.ito_reduced_units() 

669 unitstr = get_unitstr(value) 

670 if unitstr[:2] == "1/": 

671 unitstr = "/" + unitstr[2:] 

672 value = mag(value) 

673 if 1e3 <= value < 1e6: 

674 valuestr = "{:,.0f}".format(value) 

675 else: 

676 valuestr = "%-.3g" % value 

677 return into % (valuestr + unitstr) 

678 

679 

680import pickle 

681from gpkit import ureg 

682ureg.define("pax = 1") 

683ureg.define("paxkm = km") 

684ureg.define("trip = 1") 

685 

686print("STARTING...") 

687 

688sol = pickle.load(open("bd.p", "rb")) 

689 

690bd = get_breakdowns(sol) 

691 

692mbd = get_model_breakdown(sol) 

693tree = crawl_modelbd(mbd) 

694graph(tree, sol, maxdepth=2, extent=20, collapse=False) 

695 

696graph(tree.branches[0].branches[1], 

697 sol, maxdepth=2, extent=20, collapse=False) 

698 

699graph(tree, sol, maxdepth=2, extent=20) 

700 

701from gpkit.tests.helpers import StdoutCaptured 

702 

703import difflib 

704 

705# key, = [vk for vk in bd if "cruisevelocity[0]" in str(vk)] 

706# tree = crawl(key, bd, sol, permissivity=2, verbosity=1) 

707# graph(tree, sol) 

708# 

709# print("\n\nPERMISSIVITY = 0") 

710# tree = crawl(sol.costposy, bd, sol, permissivity=0) 

711# graph(tree, sol) 

712# 

713# print("\n\nPERMISSIVITY = 0.6") 

714# tree = crawl(sol.costposy, bd, sol, permissivity=0.6) 

715# graph(tree, sol) 

716# 

717# print("\n\nPERMISSIVITY = 1") 

718# tree = crawl(sol.costposy, bd, sol, permissivity=1) 

719# graph(tree, sol) 

720# 

721print("\n\nPERMISSIVITY = 2") 

722tree = crawl(sol.costposy, bd, sol, permissivity=2) 

723graph(tree, sol) 

724 

725keys = sorted((key for key in bd.keys() if not key.idx or len(key.shape) == 1), 

726 key=lambda k: str(k)) 

727 

728permissivity = 2 

729 

730# with StdoutCaptured("breakdowns.log"): 

731# for key in keys: 

732# tree = crawl(key, bd, sol, permissivity=permissivity) 

733# graph(tree, sol) 

734 

735# with StdoutCaptured("breakdowns.log.new"): 

736# for key in keys: 

737# tree = crawl(key, bd, sol, permissivity=permissivity) 

738# graph(tree, sol) 

739# 

740# with open("breakdowns.log", "r") as original: 

741# with open("breakdowns.log.new", "r") as new: 

742# diff = difflib.unified_diff( 

743# original.readlines(), 

744# new.readlines(), 

745# fromfile="original", 

746# tofile="new", 

747# ) 

748# for line in diff: 

749# print(line[:-1]) 

750 

751print("DONE")