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
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
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
9# pylint: disable=too-many-locals
10def modelinteract(model, fns_of_sol, ranges=None, **solvekwargs):
11 """Easy model interaction in IPython / Jupyter
13 By default, this creates a model with sliders for every constant
14 which prints a new solution table whenever the sliders are changed.
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.
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)
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
68 if not hasattr(fns_of_sol, "__iter__"):
69 fns_of_sol = [fns_of_sol]
71 solvekwargs["verbosity"] = 0
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()
93 resolve()
95 return widgets.interactive(resolve, **ranges_out)
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
102 Like interact(), but with the ability to control sliders and their showvars
103 live. args and kwargs are passed on to interact()
104 """
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
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 ()))
120 __defaultfntable = __defaultfn
122 fns_of_sol = [__defaultfntable]
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)
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")
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)
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)
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
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")])
194 tabs.set_title(0, 'Sliders')
196 return tabs
199def create_settings(box):
200 "Creates a widget Container for settings and info of a particular slider."
201 _, slider, sl_units = box.children
203 enable = widgets.Checkbox(value=box.visible, width="3ex")
204 link((box, 'visible'), (enable, 'value'))
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"))
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")
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"
248 return widgets.HBox(children=[enable, setvalue, units, descr,
249 fromlabel, setmin, tolabel, setmax],
250 width="105ex")