Coverage for gpkit/interactive/widgets.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

155 statements  

1"Interactive GPkit widgets for iPython notebook" 

2import ipywidgets as widgets 

3from traitlets import link 

4from ..small_scripts import is_sweepvar 

5from ..small_classes import Numbers 

6from ..exceptions import InvalidGPConstraint 

7 

8 

9# pylint: disable=too-many-locals 

10def modelinteract(model, fns_of_sol, ranges=None, **solvekwargs): 

11 """Easy model interaction in IPython / Jupyter 

12 

13 By default, this creates a model with sliders for every constant 

14 which prints a new solution table whenever the sliders are changed. 

15 

16 Arguments 

17 --------- 

18 fn_of_sol : function 

19 The function called with the solution after each solve that 

20 displays the result. By default prints a table. 

21 

22 ranges : dictionary {str: Slider object or tuple} 

23 Determines which sliders get created. Tuple values may contain 

24 two or three floats: two correspond to (min, max), while three 

25 correspond to (min, step, max) 

26 

27 **solvekwargs 

28 kwargs which get passed to the solve()/localsolve() method. 

29 """ 

30 ranges_out = {} 

31 if ranges: 

32 if not isinstance(ranges, dict): 

33 ranges = {k: None for k in ranges} 

34 slider_vars = set() 

35 for k in list(ranges): 

36 if k in model.substitutions: # only if already a constant 

37 for key in model.varkeys[k]: 

38 slider_vars.add(key) 

39 ranges[key] = ranges[k] 

40 del ranges[k] 

41 else: 

42 slider_vars = model.substitutions 

43 for k in slider_vars: 

44 v = model.substitutions[k] 

45 if is_sweepvar(v) or isinstance(v, Numbers): 

46 if is_sweepvar(v): 

47 # By default, sweep variables become sliders, so we 

48 # need to to update the model's substitutions such that 

49 # the first solve (with no substitutions) reflects 

50 # the substitutions created on slider movement 

51 _, sweep = v 

52 v = sweep[0] 

53 model.substitutions.update({k: v}) 

54 vmin, vmax = v/2.0, v*2.0 

55 if is_sweepvar(v): 

56 vmin = min(vmin, min(sweep)) 

57 vmax = max(vmax, min(sweep)) 

58 if ranges and ranges[k]: 

59 vmin, vmax = ranges[k] 

60 vstep = (vmax-vmin)/24.0 

61 varkey_latex = "$"+k.latex(excluded=["lineage"])+"$" 

62 floatslider = widgets.FloatSlider(min=vmin, max=vmax, 

63 step=vstep, value=v, 

64 description=varkey_latex) 

65 floatslider.varkey = k 

66 ranges_out[str(k)] = floatslider 

67 

68 if not hasattr(fns_of_sol, "__iter__"): 

69 fns_of_sol = [fns_of_sol] 

70 

71 solvekwargs["verbosity"] = 0 

72 

73 def resolve(**subs): 

74 "This method gets called each time the user changes something" 

75 model.substitutions.update(subs) 

76 try: 

77 try: 

78 model.solve(**solvekwargs) 

79 except InvalidGPConstraint: 

80 # TypeError raised by as_hmapslt1 in non-GP-compatible models 

81 model.localsolve(**solvekwargs) 

82 if hasattr(model, "solution"): 

83 sol = model.solution 

84 else: 

85 sol = model.result 

86 for fn in fns_of_sol: 

87 fn(sol) 

88 except RuntimeWarning as e: 

89 print("RuntimeWarning:", str(e).split("\n")[0]) 

90 print("\n> Running model.debug()") 

91 model.debug() 

92 

93 resolve() 

94 

95 return widgets.interactive(resolve, **ranges_out) 

96 

97 

98# pylint: disable=too-many-locals, too-many-statements 

99def modelcontrolpanel(model, showvars=(), fns_of_sol=None, **solvekwargs): 

100 """Easy model control in IPython / Jupyter 

101 

102 Like interact(), but with the ability to control sliders and their showvars 

103 live. args and kwargs are passed on to interact() 

104 """ 

105 

106 freevars = set(model.varkeys).difference(model.substitutions) 

107 freev_in_showvars = False 

108 for var in showvars: 

109 if var in model.varkeys and freevars.intersection(model.varkeys[var]): 

110 freev_in_showvars = True 

111 break 

112 

113 if fns_of_sol is None: 

114 def __defaultfn(solution): 

115 "Display function to run when a slider is moved." 

116 # NOTE: if there are some freevariables in showvars, filter 

117 # the table to show only those and the slider constants 

118 print(solution.summary(showvars if freev_in_showvars else ())) 

119 

120 __defaultfntable = __defaultfn 

121 

122 fns_of_sol = [__defaultfntable] 

123 

124 sliders = model.interact(fns_of_sol, showvars, **solvekwargs) 

125 sliderboxes = [] 

126 for sl in sliders.children: 

127 cb = widgets.Checkbox(value=True, width="3ex") 

128 unit_latex = sl.varkey.latex_unitstr() 

129 if unit_latex: 

130 unit_latex = r"$"+unit_latex+"$" 

131 units = widgets.Label(value=unit_latex) 

132 units.font_size = "1.16em" 

133 box = widgets.HBox(children=[cb, sl, units]) 

134 link((box, 'visible'), (cb, 'value')) 

135 sliderboxes.append(box) 

136 

137 widgets_css = widgets.HTML("""<style> 

138 [style="font-size: 1.16em;"] { padding-top: 0.25em; } 

139 [style="width: 3ex; font-size: 1.165em;"] { padding-top: 0.2em; } 

140 .widget-numeric-text { width: auto; } 

141 .widget-numeric-text .widget-label { width: 20ex; } 

142 .widget-numeric-text .form-control { background: #fbfbfb; width: 8.5ex; } 

143 .widget-slider .widget-label { width: 20ex; } 

144 .widget-checkbox .widget-label { width: 15ex; } 

145 .form-control { border: none; box-shadow: none; } 

146 </style>""") 

147 settings = [widgets_css] 

148 for sliderbox in sliderboxes: 

149 settings.append(create_settings(sliderbox)) 

150 sweep = widgets.Checkbox(value=False, width="3ex") 

151 label = ("Plot top sliders against: (separate with two spaces)") 

152 boxlabel = widgets.Label(value=label, width="200ex") 

153 y_axes = widgets.Text(value="none", width="20ex") 

154 

155 def append_plotfn(): 

156 "Creates and adds plotfn to fn_of_sols" 

157 from .plot_sweep import plot_1dsweepgrid 

158 yvars = [model.cost] 

159 for varname in y_axes.value.split(" "): # pylint: disable=no-member 

160 varname = varname.strip() 

161 try: 

162 yvars.append(model[varname]) 

163 except: # pylint: disable=bare-except 

164 break 

165 ranges = {} 

166 for sb in sliderboxes[1:]: 

167 if sb.visible and len(ranges) < 3: 

168 slider = sb.children[1] 

169 ranges[slider.varkey] = (slider.min, slider.max) 

170 

171 def __defaultfn(sol): 

172 "Plots a 1D sweep grid, starting from a single solution" 

173 fig, _ = plot_1dsweepgrid(model, ranges, yvars, origsol=sol, 

174 verbosity=0, solver="mosek_cli") 

175 fig.show() 

176 fns_of_sol.append(__defaultfn) 

177 

178 def redo_plots(_): 

179 "Refreshes the plotfn" 

180 if fns_of_sol and fns_of_sol[-1].__name__ == "__defaultfn": 

181 fns_of_sol.pop() # get rid of the old one! 

182 if sweep.value: 

183 append_plotfn() 

184 if not fns_of_sol: 

185 fns_of_sol.append(__defaultfntable) 

186 sl.value = sl.value*(1.000001) # pylint: disable=undefined-loop-variable 

187 

188 sweep.observe(redo_plots, "value") 

189 y_axes.on_submit(redo_plots) 

190 sliderboxes.insert(0, widgets.HBox(children=[sweep, boxlabel, y_axes])) 

191 tabs = widgets.Tab(children=[widgets.VBox(children=sliderboxes, 

192 padding="1.25ex")]) 

193 

194 tabs.set_title(0, 'Sliders') 

195 

196 return tabs 

197 

198 

199def create_settings(box): 

200 "Creates a widget Container for settings and info of a particular slider." 

201 _, slider, sl_units = box.children 

202 

203 enable = widgets.Checkbox(value=box.visible, width="3ex") 

204 link((box, 'visible'), (enable, 'value')) 

205 

206 def slider_link(obj, attr): 

207 "Function to link one object to an attr of the slider." 

208 # pylint: disable=unused-argument 

209 def link_fn(name, new_value): 

210 "How to update the object's value given min/max on the slider. " 

211 if new_value >= slider.max: 

212 slider.max = new_value 

213 # if any value is greater than the max, the max slides up 

214 # however, this is not held true for the minimum, because 

215 # during typing the max or value will grow, and we don't want 

216 # to permanently anchor the minimum to unfinished typing 

217 if attr == "max" and new_value <= slider.value: 

218 if slider.max >= slider.min: 

219 slider.value = new_value 

220 else: 

221 pass # bounds nonsensical, probably because we picked up 

222 # a small value during user typing. 

223 elif attr == "min" and new_value >= slider.value: 

224 slider.value = new_value 

225 setattr(slider, attr, new_value) 

226 slider.step = (slider.max - slider.min)/24.0 

227 obj.on_trait_change(link_fn, "value") 

228 link((slider, attr), (obj, "value")) 

229 

230 text_html = "<span class='form-control' style='width: auto;'>" 

231 setvalue = widgets.FloatText(value=slider.value, 

232 description=slider.description) 

233 slider_link(setvalue, "value") 

234 fromlabel = widgets.HTML(text_html + "from") 

235 setmin = widgets.FloatText(value=slider.min, width="10ex") 

236 slider_link(setmin, "min") 

237 tolabel = widgets.HTML(text_html + "to") 

238 setmax = widgets.FloatText(value=slider.max, width="10ex") 

239 slider_link(setmax, "max") 

240 

241 units = widgets.Label() 

242 units.width = "6ex" 

243 units.font_size = "1.165em" 

244 link((sl_units, 'value'), (units, 'value')) 

245 descr = widgets.HTML(text_html + slider.varkey.descr.get("label", "")) 

246 descr.width = "40ex" 

247 

248 return widgets.HBox(children=[enable, setvalue, units, descr, 

249 fromlabel, setmin, tolabel, setmax], 

250 width="105ex")