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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3043 - (show annotations) (download) (as text)
Wed Aug 5 12:43:22 2015 UTC (6 years, 10 months ago) by adrian
File MIME type: text/x-python
File size: 14554 byte(s)
Added celsius temperatures to study dialog
1 from gi.repository import Gtk, Gdk
2 from gi.repository import Pango
3 import ascpy
4 from celsiusunits import CelsiusUnits
5 from preferences import Preferences
6
7 from varentry import *
8 from studyreporter import *
9 from math import log, exp
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 ##### CELSIUS TEMPERATURE WORKAROUND
101 if self.instance.getType().isRefinedReal() and str(self.instance.getType().getDimensions()) == 'TMP':
102 units = Preferences().getPreferredUnitsOrigin(str(self.instance.getType().getName()))
103 if units == CelsiusUnits.get_celsius_sign():
104 self.convert = True
105 ##### CELSIUS TEMPERATURE WORKAROUND
106
107 for _k,_v in _arr.iteritems():
108 _t = str(_v / _conversion)+" "+_u
109 ##### CELSIUS TEMPERATURE WORKAROUND
110 if self.convert:
111 _t = CelsiusUnits.convert_kelvin_to_celsius(_v, str(self.instance.getType())) + " " + CelsiusUnits.get_celsius_sign()
112 ##### CELSIUS TEMPERATURE WORKAROUND
113 _k.set_text(_t)
114
115 self.browser.builder.connect_signals(self)
116 self.lowerb.select_region(0, -1)
117
118 def get_step_type(self):
119 _s = self.step_menu.get_active_iter()
120 return self.step_menu_model.get_value(_s,0)
121
122 def set_step_type(self,st):
123 #FIXME this depends on the ordering being equal to the 'id' column values?
124 self.step_menu.set_active(st)
125
126 def set_dist(self,dist):
127 #FIXME this depends on the ordering, is there a better way?
128 self.dist.set_active({DIST_LINEAR:0, DIST_LOG:1}[dist])
129
130 def run(self):
131 while 1:
132 _res = self.studywin.run();
133 if _res == Gtk.ResponseType.OK:
134 if self.validate_inputs():
135 # store inputs for later recall
136 _p = self.browser.prefs
137 _p.setStringPref("Study", "nsteps", self.nsteps_number)
138 _p.setStringPref("Study", "nsteps_increm", self.nsteps_increm)
139 _p.setStringPref("Study", "nsteps_ratio", self.nsteps_ratio)
140 _p.setStringPref("Study", "nsteps_type", self.get_step_type())
141 # run the study
142 self.solve()
143 break
144 else:
145 self.browser.reporter.reportError("Please review input errors in Study dialog.")
146 continue
147 elif _res==Gtk.ResponseType.CANCEL:
148 # cancel... exit Study
149 break
150 self.studywin.destroy()
151
152 def on_studywin_close(self,*args):
153 self.studywin.response(Gtk.ResponseType.CANCEL)
154
155 def on_key_press_event(self,widget,event):
156 keyname = Gdk.keyval_name(event.keyval)
157 if keyname=="Return":
158 self.studywin.response(Gtk.ResponseType.OK)
159 return True
160 elif keyname=="Escape":
161 self.studywin.response(Gtk.ResponseType.CANCEL)
162 return True;
163 return False;
164
165 def on_methodrun_changed(self, *args):
166 index = self.methodrun.get_active()
167 piter = self.methodrun.get_model().get_iter(Gtk.TreePath.new_from_string(str(index)))
168 _sel = self.methodrun.get_model().get_value(piter, 0)
169 if _sel:
170 _methods = self.browser.sim.getType().getMethods()
171 for _m in _methods:
172 if _m.getName()==_sel:
173 self.method = _m
174 break
175
176 def on_dist_changed(self, *args):
177 _dist = self.dist.get_active_text()
178 if _dist == DIST_LINEAR:
179 if self.get_step_type() == STEP_RATIO:
180 self.set_step_type(STEP_INCREM)
181 self.nsteps.set_text(self.nsteps_number)
182 elif _dist == DIST_LOG:
183 if self.get_step_type() == STEP_INCREM:
184 self.set_step_type(STEP_RATIO)
185 self.nsteps.set_text(self.nsteps_ratio)
186 self.validate_inputs()
187
188 def validate_inputs(self):
189 """
190 Check that inputs make sense in terms of log dist constraints & others.
191 Returns 1 if all is valid. If all is not valid, relevant inputs are
192 tainted for user correction.
193 """
194 _dist = self.dist.get_active_text()
195
196 _start = self.parse_entry(self.lowerb)
197 _end = self.parse_entry(self.upperb)
198 steps = self.validate_nsteps()
199
200 if _start is None or _end is None:
201 # error/empty start/end values will already have been tainted
202 self.taint_dist()
203 return 0
204
205 if _start == _end:
206 # can't distribute over a zero-width range (and no point)
207 _msg = "Bounds cannot not be equal."
208 self.taint_dist(msg=_msg)
209 self.taint_entry(self.lowerb,msg=_msg)
210 self.taint_entry(self.upperb,msg=_msg)
211 return 0
212
213 if _dist == DIST_LINEAR:
214 flag = 0
215 if self.get_step_type() == STEP_RATIO:
216 self.set_step_type(STEP_INCREM)
217 self.taint_dist(good=1)
218 return 1
219 if _dist == DIST_LOG:
220 flag = 0
221 if self.get_step_type() == STEP_INCREM:
222 self.set_step_type(STEP_RATIO)
223 flag = 1
224 if flag == 1:
225 self.step_menu.set_active(1)
226 if _start == 0 or _end == 0:
227 _msg = "Bounds cannot be 0 for logarithmic distribution."
228 self.taint_dist(_msg)
229 if _start == 0:
230 self.taint_entry(self.lowerb,msg=_msg)
231 else:
232 self.taint_entry(self.upperb,msg=_msg)
233 return 0
234 if (_start/_end) < 0:
235 _msg = "Bounds cannot be of opposite sign for logarithmic distribution."
236 self.taint_dist(_msg)
237 self.taint_entry(self.lowerb,msg=_msg)
238 self.taint_entry(self.upperb,msg=_msg)
239 return 0
240 self.check_dist.set_from_stock('gtk-yes', Gtk.IconSize.BUTTON)
241 self.check_dist.set_tooltip_text("")
242 return 1
243
244 def on_step_menu_changed(self, *args):
245 """
246 If the step menu is changed, recall previous 'sane' input for nsteps.
247 """
248 _st = self.get_step_type()
249 self.nsteps.set_text({
250 STEP_NUMBER:self.nsteps_number
251 ,STEP_INCREM:self.nsteps_increm
252 ,STEP_RATIO:self.nsteps_ratio
253 }[_st])
254 _dist = self.dist.get_active_text()
255 if _st==STEP_RATIO and _dist==DIST_LINEAR:
256 self.set_dist(DIST_LOG)
257 elif _st==STEP_INCREM and _dist==DIST_LOG:
258 self.set_dist(DIST_LINEAR)
259 self.validate_nsteps()
260
261 def validate_nsteps(self):
262 _st = self.get_step_type()
263 _val = self.nsteps.get_text()
264 if _st==STEP_RATIO:
265 try:
266 _fl = float(_val)
267 except:
268 _fl = None
269 if _fl is None or _fl <= 0:
270 self.taint_entry(self.nsteps,"Value must be positive")
271 return 0
272 elif _st==STEP_INCREM:
273 # will also handle the tainting, if required:
274 return self.parse_entry(self.nsteps)
275 elif _st==STEP_NUMBER:
276 try:
277 _int = int(float(_val))
278 except:
279 _int = 0
280 if _val != _int or _int < 2:
281 self.taint_entry(self.nsteps,"Number of steps must be positive integer >= 2")
282 return 0
283 self.nsteps.set_text(_int)
284 self.taint_entry(self.nsteps, good=1)
285
286 def on_nsteps_changed(self, *args):
287 self.validate_nsteps()
288 setattr(self,("nsteps_number","nsteps_increm","nsteps_ratio")[self.get_step_type()],self.nsteps.get_text())
289
290 def on_check_toggled(self, *args):
291 # update the preference for behaviour on solver fail
292 _p = self.browser.prefs
293 _p.setBoolPref("StudyReporter", "continue_on_fail", self.checkbutton.get_active())
294
295 def taint_entry(self, entry, good=0, msg=None):
296 """
297 Color an input box red/white according to value of 'good'; add a further
298 error message via the tooltip on the secondary icon, if provided.
299 """
300 color = "white"
301 if not good:
302 color = "#FFBBBB"
303 for s in [Gtk.StateType.NORMAL, Gtk.StateType.ACTIVE]:
304 entry.modify_bg(s, Gdk.color_parse(color))
305 entry.modify_base(s, Gdk.color_parse(color))
306 # FIXME don't apply logic to hard-wired colour codes
307 if not good:
308 entry.set_property("secondary-icon-stock", 'gtk-dialog-error')
309 else:
310 entry.set_property("secondary-icon-stock", 'gtk-yes')
311
312 # causes gtk-critical errors
313 # entry.set_property("secondary-icon-tooltip-text", "")
314 # entry.set_property("secondary-icon-tooltip-text", msg)
315
316 def taint_dist(self, good=0, msg=None):
317 """
318 Taint the distribution combobox, using the icon placed to its right.
319 Add a message as tooltip on the error icon, if provided.
320 """
321 if good:
322 _icon = 'gtk-yes'
323 else:
324 _icon = 'gtk-dialog-error'
325 self.check_dist.set_from_stock(_icon, Gtk.IconSize.BUTTON)
326 if msg!=None:
327 self.check_dist.set_tooltip_text(msg)
328
329 def parse_entry(self, entry):
330 """
331 Parse an input box and enforce dimensional agreement with self.instance.
332 """
333 newtext = entry.get_text()
334 ##### CELSIUS TEMPERATURE WORKAROUND
335 if self.convert:
336 if len(newtext) > 0 and (len(newtext.split(" ")) == 1 or newtext.split(" ")[1] == CelsiusUnits.get_celsius_sign()):
337 newtext = CelsiusUnits.convert_celsius_to_kelvin(newtext.split(" ")[0], str(self.instance.getType()))
338 ##### CELSIUS TEMPERATURE WORKAROUND
339 # FIXME Add missing units if they have not been entered.
340 i = RealAtomEntry(self.instance, newtext)
341 _msg = None
342 try:
343 i.checkEntry()
344 _value = i.getValue()
345 except InputError, e:
346 # FIXME does the following line actually clear out the entry box?
347 _value = None
348 _error = re.split('Input Error: ', str(e), 1)
349 _msg = _error[1]
350
351 if _value is not None:
352 self.taint_entry(entry, good=1)
353 else:
354 self.taint_entry(entry, msg=_msg)
355 return _value
356
357 def solve(self):
358 # we can assume that all the inputs have been validated
359 _start = self.parse_entry(self.lowerb)
360 _end = self.parse_entry(self.upperb)
361
362 _dist = self.dist.get_active_text()
363 _st = self.get_step_type()
364 _step = None
365 _nsteps = None
366 if _dist==DIST_LINEAR:
367 if _st==STEP_NUMBER:
368 _nsteps = int(self.nsteps.get_text())
369 _step = (_end - _start)/(_nsteps)
370 elif _st==STEP_INCREM:
371 _step = self.parse_entry(self.nsteps)
372 # TODO convert to real value without units?
373 _nsteps = int((_end - _start)/_step)
374 elif _dist==DIST_LOG:
375 if _st==STEP_RATIO:
376 _step = log(float(self.nsteps.get_text()))
377 _nsteps = int((log(_end) - log(_start))/_step)
378 elif _st==STEP_NUMBER:
379 _nsteps = int(self.nsteps.get_text())
380 _step = (log(_end) - log(_start))/_nsteps
381
382 if _step is None or _nsteps is None:
383 raise RuntimeError("invalid step selection")
384
385 _b = self.browser
386
387 if not hasattr(self.browser,'solver'):
388 _b.reporter.reportError("No solver assigned!")
389 return
390
391 if _b.no_built_system():
392 return
393 _b.start_waiting("Solving with %s..." % _b.solver.getName())
394 self.studywin.destroy()
395 reporter = StudyReporter(_b, _b.sim.getNumVars(), self.instance, _nsteps, self)
396
397 # FIXME move following code to the StudyReporter class?
398 i = 0
399 _val = _start
400 while i <= _nsteps and reporter.guiinterrupt is False:
401 # run a method, if requested
402 if self.method:
403 try:
404 _b.sim.run(self.method)
405 except RuntimeError,e:
406 _b.reporter.reportError(str(e))
407
408 # set the value (do it inside the loop to avoid METHOD possibly unfixing)
409 if self.instance.getType().isRefinedSolverVar():
410 # for solver vars, set the 'fixed' flag as well
411 ## FIXME this function seems to somehow be repeatedly parsing units: avoid doing that every step.
412 self.instance.setFixedValue(_val)
413 else:
414 # what other kind of variable is it possible to study, if not a solver_var? integer? not suppported?
415 self.instance.setRealValue(_val)
416
417 #solve
418 # FIXME where is the continue_on_fail thing?
419 try:
420 reporter.updateVarDetails(i)
421 _b.sim.solve(_b.solver, reporter)
422 except RuntimeError,e:
423 _b.reporter.reportError(str(e))
424
425 i += 1
426 # any issue with accumulation of rounding errors here?
427 if _dist==DIST_LOG:
428 _val = exp(log(_start)+i*_step)
429 else:
430 _val = _start + i*_step
431
432 if reporter.continue_on_fail == True:
433 reporter.updateVarDetails(i)
434
435 _b.stop_waiting()
436 _b.modelview.refreshtree()
437

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