/[ascend]/branches/adrian/pygtk/study.py
ViewVC logotype

Contents of /branches/adrian/pygtk/study.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3076 - (show annotations) (download) (as text)
Wed Aug 19 13:34:55 2015 UTC (6 years, 10 months ago) by adrian
File MIME type: text/x-python
File size: 15129 byte(s)
Fixed observer values displaying issue
1 import threading
2 from math import log, exp
3
4 from gi.overrides import GLib
5 from gi.repository import Gdk
6
7 from celsiusunits import CelsiusUnits
8 from varentry import *
9 from studyreporter import *
10
11 STEP_NUMBER = 0
12 STEP_INCREM = 1
13 STEP_RATIO = 2
14 DIST_LINEAR = "Linear"
15 DIST_LOG = "Logarithmic"
16
17 class StudyWin:
18 def __init__(self, browser, instance):
19 """
20 Study dialog: allow user to fill an Observer by varying the value in
21 a particular column, and solving for each case (also there is the option
22 of running a selected METHOD before each step)
23 """
24 # we will be using the instance to determine valid units for bounds and step size
25 self.instance = instance
26 self.browser = browser
27 self.browser.builder.add_objects_from_file(self.browser.glade_file,["list_of_dist","studywin"])
28
29 widgets = ["lowerb","upperb","step_menu","nsteps","methodrun","dist","check_dist","studywin","var_to_study"]
30 for n in widgets:
31 setattr(self,n,self.browser.builder.get_object(n))
32
33 self.checkbutton = self.browser.builder.get_object("on_fail_continue")
34 self.method = None
35
36 # TODO add an integer index to the ListStore as well, to avoid string conversion
37 self.step_menu_model = Gtk.ListStore(int,str)
38 self.step_menu_model.append([STEP_NUMBER,'No. of steps'])
39 self.step_menu_model.append([STEP_INCREM,'Step size'])
40 self.step_menu_model.append([STEP_RATIO, 'Step ratio'])
41 renderer = Gtk.CellRendererText()
42 self.step_menu.set_model(self.step_menu_model)
43 self.step_menu.pack_start(renderer, True)
44 self.step_menu.add_attribute(renderer, 'text',1)
45 self.step_menu.set_active(0)
46 #self.step_type="No. of steps"
47
48 _p = self.browser.prefs
49 _continue_on_fail = _p.getBoolPref("StudyReporter", "continue_on_fail", True)
50 self.checkbutton.set_active(_continue_on_fail)
51
52 # set up the distributions combobox
53 dist_model = Gtk.ListStore(str)
54 dist_model.append([DIST_LINEAR])
55 dist_model.append([DIST_LOG])
56 self.dist.set_model(dist_model)
57 self.dist.set_active(0)
58
59 # set up the methods combobox
60 _methodstore = self.browser.methodstore
61 _methodrenderer = Gtk.CellRendererText()
62 self.methodrun.set_model(_methodstore)
63 self.methodrun.pack_start(_methodrenderer, True)
64 self.methodrun.add_attribute(_methodrenderer, 'text',0)
65
66 # user preferences for this dialog
67 self.nsteps_number = str(int(float(self.browser.prefs.getStringPref("Study","nsteps","10"))))
68 self.nsteps_increm = self.browser.prefs.getStringPref("Study","nsteps_increm","")
69 self.nsteps_ratio = self.browser.prefs.getStringPref("Study","nsteps_ratio","1.1")
70 self.nsteps_type = int(self.browser.prefs.getStringPref("Study","step_type",str(STEP_NUMBER)))
71
72 # fill in step type and default nsteps
73 self.var_to_study.set_text(self.browser.sim.getInstanceName(self.instance))
74 self.set_step_type(self.nsteps_type)
75 self.nsteps.set_text({
76 STEP_NUMBER:self.nsteps_number
77 ,STEP_INCREM:self.nsteps_increm
78 ,STEP_RATIO:self.nsteps_ratio
79 }[self.nsteps_type])
80 # if using an increment by preference, only permit it if dimensionally compatible with
81 # selected variable, else default back to number of steps
82 if not self.validate_nsteps():
83 self.set_step_type(STEP_NUMBER)
84 self.nsteps.set_text(self.nsteps_number)
85 self.taint_entry(self.nsteps,good=1)
86
87 # fill in upper/.lower bound
88 _u = self.instance.getType().getPreferredUnits();
89 if _u is None:
90 _conversion = 1
91 _u = self.instance.getDimensions().getDefaultUnits().getName().toString()
92 else:
93 _conversion = _u.getConversion() # displayvalue x conversion = SI
94 _u = _u.getName().toString()
95
96 _arr = {self.lowerb: self.instance.getRealValue()
97 ,self.upperb: self.instance.getUpperBound() # this upper bound is probably stoopid
98 }
99
100 for _k,_v in _arr.iteritems():
101 _t = str(_v / _conversion)+" "+_u
102 ##### CELSIUS TEMPERATURE WORKAROUND
103 _t = CelsiusUnits.convert_show(self.instance, str(_v), True, default=_t)
104 ##### CELSIUS TEMPERATURE WORKAROUND
105 _k.set_text(_t)
106
107 self.browser.builder.connect_signals(self)
108 self.lowerb.select_region(0, -1)
109 self.solve_interrupt = False
110 self.data = {}
111
112 def get_step_type(self):
113 _s = self.step_menu.get_active_iter()
114 return self.step_menu_model.get_value(_s,0)
115
116 def set_step_type(self,st):
117 #FIXME this depends on the ordering being equal to the 'id' column values?
118 self.step_menu.set_active(st)
119
120 def set_dist(self,dist):
121 #FIXME this depends on the ordering, is there a better way?
122 self.dist.set_active({DIST_LINEAR:0, DIST_LOG:1}[dist])
123
124 def run(self):
125 while 1:
126 _res = self.studywin.run();
127 if _res == Gtk.ResponseType.OK:
128 if self.validate_inputs():
129 # store inputs for later recall
130 _p = self.browser.prefs
131 _p.setStringPref("Study", "nsteps", self.nsteps_number)
132 _p.setStringPref("Study", "nsteps_increm", self.nsteps_increm)
133 _p.setStringPref("Study", "nsteps_ratio", self.nsteps_ratio)
134 _p.setStringPref("Study", "nsteps_type", self.get_step_type())
135 # run the study
136 self.solve()
137 break
138 else:
139 self.browser.reporter.reportError("Please review input errors in Study dialog.")
140 continue
141 elif _res==Gtk.ResponseType.CANCEL:
142 # cancel... exit Study
143 break
144 self.studywin.destroy()
145
146 def on_studywin_close(self,*args):
147 self.studywin.response(Gtk.ResponseType.CANCEL)
148
149 def on_key_press_event(self,widget,event):
150 keyname = Gdk.keyval_name(event.keyval)
151 if keyname=="Return":
152 self.studywin.response(Gtk.ResponseType.OK)
153 return True
154 elif keyname=="Escape":
155 self.studywin.response(Gtk.ResponseType.CANCEL)
156 return True;
157 return False;
158
159 def on_methodrun_changed(self, *args):
160 index = self.methodrun.get_active()
161 piter = self.methodrun.get_model().get_iter(Gtk.TreePath.new_from_string(str(index)))
162 _sel = self.methodrun.get_model().get_value(piter, 0)
163 if _sel:
164 _methods = self.browser.sim.getType().getMethods()
165 for _m in _methods:
166 if _m.getName()==_sel:
167 self.method = _m
168 break
169
170 def on_dist_changed(self, *args):
171 _dist = self.dist.get_active_text()
172 if _dist == DIST_LINEAR:
173 if self.get_step_type() == STEP_RATIO:
174 self.set_step_type(STEP_INCREM)
175 self.nsteps.set_text(self.nsteps_number)
176 elif _dist == DIST_LOG:
177 if self.get_step_type() == STEP_INCREM:
178 self.set_step_type(STEP_RATIO)
179 self.nsteps.set_text(self.nsteps_ratio)
180 self.validate_inputs()
181
182 def validate_inputs(self):
183 """
184 Check that inputs make sense in terms of log dist constraints & others.
185 Returns 1 if all is valid. If all is not valid, relevant inputs are
186 tainted for user correction.
187 """
188 _dist = self.dist.get_active_text()
189
190 _start = self.parse_entry(self.lowerb)
191 _end = self.parse_entry(self.upperb)
192 steps = self.validate_nsteps()
193
194 if _start is None or _end is None:
195 # error/empty start/end values will already have been tainted
196 self.taint_dist()
197 return 0
198
199 if _start == _end:
200 # can't distribute over a zero-width range (and no point)
201 _msg = "Bounds cannot not be equal."
202 self.taint_dist(msg=_msg)
203 self.taint_entry(self.lowerb,msg=_msg)
204 self.taint_entry(self.upperb,msg=_msg)
205 return 0
206
207 if _dist == DIST_LINEAR:
208 flag = 0
209 if self.get_step_type() == STEP_RATIO:
210 self.set_step_type(STEP_INCREM)
211 self.taint_dist(good=1)
212 return 1
213 if _dist == DIST_LOG:
214 flag = 0
215 if self.get_step_type() == STEP_INCREM:
216 self.set_step_type(STEP_RATIO)
217 flag = 1
218 if flag == 1:
219 self.step_menu.set_active(1)
220 if _start == 0 or _end == 0:
221 _msg = "Bounds cannot be 0 for logarithmic distribution."
222 self.taint_dist(_msg)
223 if _start == 0:
224 self.taint_entry(self.lowerb,msg=_msg)
225 else:
226 self.taint_entry(self.upperb,msg=_msg)
227 return 0
228 if (_start/_end) < 0:
229 _msg = "Bounds cannot be of opposite sign for logarithmic distribution."
230 self.taint_dist(_msg)
231 self.taint_entry(self.lowerb,msg=_msg)
232 self.taint_entry(self.upperb,msg=_msg)
233 return 0
234 self.check_dist.set_from_stock('gtk-yes', Gtk.IconSize.BUTTON)
235 self.check_dist.set_tooltip_text("")
236 return 1
237
238 def on_step_menu_changed(self, *args):
239 """
240 If the step menu is changed, recall previous 'sane' input for nsteps.
241 """
242 _st = self.get_step_type()
243 self.nsteps.set_text({
244 STEP_NUMBER:self.nsteps_number
245 ,STEP_INCREM:self.nsteps_increm
246 ,STEP_RATIO:self.nsteps_ratio
247 }[_st])
248 _dist = self.dist.get_active_text()
249 if _st==STEP_RATIO and _dist==DIST_LINEAR:
250 self.set_dist(DIST_LOG)
251 elif _st==STEP_INCREM and _dist==DIST_LOG:
252 self.set_dist(DIST_LINEAR)
253 self.validate_nsteps()
254
255 def validate_nsteps(self):
256 _st = self.get_step_type()
257 _val = self.nsteps.get_text()
258 if _st==STEP_RATIO:
259 try:
260 _fl = float(_val)
261 except:
262 _fl = None
263 if _fl is None or _fl <= 0:
264 self.taint_entry(self.nsteps,"Value must be positive")
265 return 0
266 elif _st==STEP_INCREM:
267 # will also handle the tainting, if required:
268 return self.parse_entry(self.nsteps)
269 elif _st==STEP_NUMBER:
270 try:
271 _int = int(float(_val))
272 except:
273 _int = 0
274 if _val != _int or _int < 2:
275 self.taint_entry(self.nsteps,"Number of steps must be positive integer >= 2")
276 return 0
277 self.nsteps.set_text(_int)
278 self.taint_entry(self.nsteps, good=1)
279
280 def on_nsteps_changed(self, *args):
281 self.validate_nsteps()
282 setattr(self,("nsteps_number","nsteps_increm","nsteps_ratio")[self.get_step_type()],self.nsteps.get_text())
283
284 def on_check_toggled(self, *args):
285 # update the preference for behaviour on solver fail
286 _p = self.browser.prefs
287 _p.setBoolPref("StudyReporter", "continue_on_fail", self.checkbutton.get_active())
288
289 def taint_entry(self, entry, good=0, msg=None):
290 """
291 Color an input box red/white according to value of 'good'; add a further
292 error message via the tooltip on the secondary icon, if provided.
293 """
294 color = "white"
295 if not good:
296 color = "#FFBBBB"
297 for s in [Gtk.StateType.NORMAL, Gtk.StateType.ACTIVE]:
298 entry.modify_bg(s, Gdk.color_parse(color))
299 entry.modify_base(s, Gdk.color_parse(color))
300 # FIXME don't apply logic to hard-wired colour codes
301 if not good:
302 entry.set_property("secondary-icon-stock", 'gtk-dialog-error')
303 else:
304 entry.set_property("secondary-icon-stock", 'gtk-yes')
305
306 # causes gtk-critical errors
307 # entry.set_property("secondary-icon-tooltip-text", "")
308 # entry.set_property("secondary-icon-tooltip-text", msg)
309
310 def taint_dist(self, good=0, msg=None):
311 """
312 Taint the distribution combobox, using the icon placed to its right.
313 Add a message as tooltip on the error icon, if provided.
314 """
315 if good:
316 _icon = 'gtk-yes'
317 else:
318 _icon = 'gtk-dialog-error'
319 self.check_dist.set_from_stock(_icon, Gtk.IconSize.BUTTON)
320 if msg!=None:
321 self.check_dist.set_tooltip_text(msg)
322
323 def parse_entry(self, entry):
324 """
325 Parse an input box and enforce dimensional agreement with self.instance.
326 """
327 newtext = entry.get_text()
328 ##### CELSIUS TEMPERATURE WORKAROUND
329 newtext = CelsiusUnits.convert_edit(self.instance, newtext, False)
330 ##### CELSIUS TEMPERATURE WORKAROUND
331 # FIXME Add missing units if they have not been entered.
332 i = RealAtomEntry(self.instance, newtext)
333 _msg = None
334 try:
335 i.checkEntry()
336 _value = i.getValue()
337 except InputError, e:
338 # FIXME does the following line actually clear out the entry box?
339 _value = None
340 _error = re.split('Input Error: ', str(e), 1)
341 _msg = _error[1]
342
343 if _value is not None:
344 self.taint_entry(entry, good=1)
345 else:
346 self.taint_entry(entry, msg=_msg)
347 return _value
348
349 def solve(self):
350 # we can assume that all the inputs have been validated
351 _start = self.parse_entry(self.lowerb)
352 _end = self.parse_entry(self.upperb)
353
354 _dist = self.dist.get_active_text()
355 _st = self.get_step_type()
356 _step = None
357 _nsteps = None
358 if _dist==DIST_LINEAR:
359 if _st==STEP_NUMBER:
360 _nsteps = int(self.nsteps.get_text())
361 _step = (_end - _start)/(_nsteps)
362 elif _st==STEP_INCREM:
363 _step = self.parse_entry(self.nsteps)
364 # TODO convert to real value without units?
365 _nsteps = int((_end - _start)/_step)
366 elif _dist==DIST_LOG:
367 if _st==STEP_RATIO:
368 _step = log(float(self.nsteps.get_text()))
369 _nsteps = int((log(_end) - log(_start))/_step)
370 elif _st==STEP_NUMBER:
371 _nsteps = int(self.nsteps.get_text())
372 _step = (log(_end) - log(_start))/_nsteps
373
374 if _step is None or _nsteps is None:
375 raise RuntimeError("invalid step selection")
376
377 _b = self.browser
378
379 if not hasattr(self.browser,'solver'):
380 _b.reporter.reportError("No solver assigned!")
381 return
382
383 if _b.no_built_system():
384 return
385 _b.start_waiting("Solving with %s..." % _b.solver.getName())
386 self.studywin.destroy()
387 reporter = StudyReporter(_b, _b.sim.getNumVars(), self.instance, _nsteps, self)
388
389 self.data = {}
390 for tab in self.browser.observers:
391 if tab.alive:
392 self.data[tab.name] = []
393 self.solve_interrupt = False
394 thread = threading.Thread(target=self.solve_thread, args=(_b, reporter, _start, _step, _nsteps, _dist))
395 thread.daemon = True
396 thread.start()
397
398 def solve_thread(self, browser, reporter, start, step, nsteps, dist):
399 i = 0
400 while i <= nsteps and not self.solve_interrupt:
401 # run a method, if requested
402 if self.method:
403 try:
404 browser.sim.run(self.method)
405 except RuntimeError, e:
406 browser.reporter.reportError(str(e))
407
408 # any issue with accumulation of rounding errors here?
409 if dist == DIST_LOG:
410 _val = exp(log(start) + i * step)
411 else:
412 _val = start + i * step
413
414 # set the value (do it inside the loop to avoid METHOD possibly unfixing)
415 if self.instance.getType().isRefinedSolverVar():
416 # for solver vars, set the 'fixed' flag as well
417 self.instance.setFixedValue(_val)
418 else:
419 # what other kind of variable is it possible to study, if not a solver_var? integer? not suppported?
420 self.instance.setRealValue(_val)
421
422 GLib.idle_add(self.solve_update, reporter, i)
423 try:
424 browser.sim.presolve(browser.solver)
425 status = browser.sim.getStatus()
426 while status.isReadyToSolve() and not self.solve_interrupt:
427 res = browser.sim.iterate()
428 status.getSimulationStatus(browser.sim)
429 GLib.idle_add(self.solve_update_step, reporter, status)
430 # 'make' some time for gui update
431 time.sleep(0.001)
432 if res != 0:
433 break
434 self.save_data()
435 GLib.idle_add(self.solve_finish_step, reporter, status)
436 browser.sim.postsolve(status)
437 except RuntimeError, err:
438 browser.reporter.reportError(str(err))
439
440 i += 1
441
442 GLib.idle_add(self.solve_update, reporter, i)
443 GLib.idle_add(self.solve_finish, browser, reporter)
444
445 def save_data(self):
446 for tab in self.browser.observers:
447 if tab.alive:
448 v = tab.get_values()
449 self.data[tab.name].append(v)
450
451 def solve_update(self, reporter, i):
452 reporter.updateVarDetails(i)
453 return False
454
455 def solve_update_step(self, reporter, status):
456 self.solve_interrupt = reporter.report(status)
457 return False
458
459 def solve_finish_step(self, reporter, status):
460 reporter.finalise(status)
461 return False
462
463 def solve_finish(self, browser, reporter):
464 reporter.report_observed(self.data)
465 browser.stop_waiting()
466 browser.modelview.refreshtree()
467 return False

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22