1 """QubX.Modeling.Utils panel; hosts a registry of generic model/data-based optimizers, and sub-panels which can use them.
2
3 Optimizer methods should be called from the gobject (gui) thread.
4 To call from a qubx.task.Robot thread, use robot.gui_call_recv with wait=False.
5
6 Copyright 2003-2014 Research Foundation State University of New York
7 This file is part of QUB Express.
8
9 QUB Express is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 QUB Express is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License,
20 named LICENSE.txt, in the QUB Express program directory. If not, see
21 <http://www.gnu.org/licenses/>.
22
23 """
24
25 import collections
26 import gobject
27 import gtk
28 from gtk import gdk
29 import numpy
30 import random
31 import traceback
32 from itertools import izip
33 from math import *
34
35 import qubx.faces
36 import qubx.global_namespace
37 import qubx.GTK
38 import qubx.settings
39 from qubx.settings import Propertied, Property
40 from qubx.accept import *
41 from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem
42 from qubx.trialset import make_trial_row, add_trial_rates
43 from qubx.util_types import *
46 """Presents a consistent interface on one model/data-based optimization routine, e.g. MIL, MSL, MacRates.
47 Created by QubX.Modeling.Utils.add_optimizer, listed in QubX.Modeling.Utils.optimizers, assignable as QubX.Modeling.Utils.optimizer.
48 Used by sub-panels of QubX.Modeling.Utils."""
49 - def __init__(self, name, get_model_prep, get_data_prep, get_ll, optimize, OnLL, OnIteration, OnOptimized):
50 """
51
52 @param name: string to identify the algorithm
53 @param get_model_prep: func(model=None, modeltree=None) -> model_prep
54 model: qubx.model.QubModel
55 modeltree: qubx.tree.Node containing QMF (only if no model provided)
56 model_prep: object derived from model, ready for computation, with fields:
57 .source: the input QubModel, if provided
58 .model: a private QubModel copy
59 .modeltree: QMF tree, even if not provided
60 since rates change so much more often, my algorithms re-use the rest of the prep when possible, by defining these fields:
61 .K0, .K1, .K2: numpy.matrix of rate constants
62 .set_rates(K0, K1): updates .K0, .K1, and .model
63 .set_rates2(K0, K1, K2): preferred newer form for pressure sensitivity
64 @param get_data_prep: func(segs, model_prep, wait=True, receiver=lambda dp: None) -> data_prep
65 segs: list of qubx.data_types.SourceSeg
66 model_prep: result of get_model_prep, since signal layout may depend on model stimuli
67 wait: False to return immediately
68 receiver: if wait, this function is called with data_prep when it's ready (on gobject thread)
69 data_prep: object with prepared data, ready for computation, with fields:
70 .segs: input the list of SourceSeg
71 .model_prep: input prepared model
72 and whatever else the algorithm needs, e.g. sampled or idealized data
73 @param get_ll: func(model_prep=None, data_prep=None, wait=True, receiver=lambda LL: None, on_trial, **kw) -> LL
74 evaluates the score for given model and data; fills them in from onscreen sources if not provided.
75 wait: False to return immediately
76 receiver: if wait == False: func(LL) called when computation is done
77 on_trial: func(model_tree, row_dict) with completed trial info
78 **kw: other keywords to apply to properties before running; e.g. use_Peq=True
79 @param optimize: func(model_prep=None, data_prep=None, wait=True, receiver=lambda LL: None, ext_return=False, on_trial, **kw) -> LL
80 iterates, adjusting parameters to improve LL for given model and data; fills them in from onscreen sources if not provided.
81 wait: False to return immediately
82 receiver: if wait == False: func(result--see ext_return) called when computation is done
83 ext_return: True to return the tuple (LL, iterations, grads, hessian); False to return LL
84 on_trial: func(model_tree, row_dict) with completed trial info
85 **kw: other keywords to apply to properties before running; e.g. use_Peq=True
86 @param OnLL: L{WeakEvent}(LL) when get_ll completes
87 @param OnIteration: L{WeakEvent}(iterations, LL, grads) each iteration during opt
88 @param OnOptimized: L{WeakEvent}(iterations, LL, grads, hessian) when optimize completes
89 """
90 Anon.__init__(self, name=name, get_model_prep=get_model_prep, get_data_prep=get_data_prep, get_ll=get_ll, optimize=optimize,
91 OnLL=OnLL, OnIteration=OnIteration, OnOptimized=OnOptimized, LL=0.0, iterations=0, grads=[], hessian=None)
92 self.__ref = Reffer()
93 OnLL += self.__ref(self.__onLL)
94 OnIteration += self.__ref(self.__onIteration)
95 OnOptimized += self.__ref(self.__onOptimized)
99 self.iterations = iterations
100 self.LL = LL
101 self.grads = grads
103 self.__onIteration(iterations, LL, grads)
104 self.hessian = hessian
105
108 """Hosts a registry of generic model/data-based optimizers, and sub-panels which can use them.
109
110 @ivar optimizers: list of L{Optimizer}
111 """
112 __explore_featured = ['optimizers', 'optimizer', 'optimizer_name', 'add_optimizer']
113 - def __init__(self, name='Utils', global_name="QubX.Modeling.Utils"):
128 optimizer = property(lambda self: self.__optimizer, lambda self, x: self.set_optimizer(x), doc='the L{Optimizer} chosen in the gui')
129 optimizer_name = property(lambda self: self.__optimizer.name, lambda self, x: self.set_optimizer_name(x), doc='name of the chosen L{Optimizer} (set this to choose by name)')
130 - def add_optimizer(self, name, get_model_prep, get_data_prep, get_ll, optimize, OnLL, OnIteration, OnOptimized):
131 """Registers a model/data-based optimization method for sub-panel use.
132
133 @param name: string to identify the algorithm
134 @param get_model_prep: func(model=None, modeltree=None) -> model_prep
135 model: qubx.model.QubModel
136 modeltree: qubx.tree.Node containing QMF (only if no model provided)
137 model_prep: object derived from model, ready for computation, with fields:
138 .source: the input QubModel, if provided
139 .model: a private QubModel copy
140 .modeltree: QMF tree, even if not provided
141 since rates change so much more often, my algorithms re-use the rest of the prep when possible, by defining these fields:
142 .K0, .K1, .K2: numpy.matrix of rate constants
143 .set_rates(K0, K1): updates .K0, .K1, and .model
144 .set_rates2(K0, K1, K2): preferred newer form for pressure sensitivity
145 @param get_data_prep: func(segs, model_prep, wait=True, receiver=lambda dp: None) -> data_prep
146 segs: list of qubx.data_types.SourceSeg
147 model_prep: result of get_model_prep, since signal layout may depend on model stimuli
148 wait: False to return immediately
149 receiver: if wait, this function is called with data_prep when it's ready (on gobject thread)
150 data_prep: object with prepared data, ready for computation, with fields:
151 .segs: input the list of SourceSeg
152 .model_prep: input prepared model
153 and whatever else the algorithm needs, e.g. sampled or idealized data
154 @param get_ll: func(model_prep=None, data_prep=None, wait=True, receiver=lambda LL: None, **kw) -> LL
155 evaluates the score for given model and data; fills them in from onscreen sources if not provided.
156 wait: False to return immediately
157 receiver: if wait == False: func(LL) called when computation is done
158 **kw: other keywords to apply to properties before running; e.g. use_Peq=True
159 @param optimize: func(model_prep=None, data_prep=None, wait=True, receiver=lambda LL: None, ext_return=False, **kw) -> LL
160 iterates, adjusting parameters to improve LL for given model and data; fills them in from onscreen sources if not provided.
161 wait: False to return immediately
162 receiver: if wait == False: func(result--see ext_return) called when computation is done
163 ext_return: True to return the tuple (LL, iterations, grads, hessian); False to return LL
164 **kw: other keywords to apply to properties before running; e.g. use_Peq=True
165 @param OnLL: L{WeakEvent}(LL) when get_ll completes
166 @param OnIteration: L{WeakEvent}(iterations, LL, grads) each iteration during opt
167 @param OnOptimized: L{WeakEvent}(iterations, LL, grads, hessian) when optimize completes
168 """
169 opt = Optimizer(name, get_model_prep, get_data_prep, get_ll, optimize, OnLL, OnIteration, OnOptimized)
170 for i, existing in enumerate(self.optimizers):
171 if existing.name == name:
172 self.optimizers[i] = opt
173 break
174 else:
175 self.optimizers.append(opt)
176 self.mnuOptimizer.add_choice(name)
177 if name == str(self.__prefs['optimizer_name'].data):
178 self.optimizer = opt
183 self.mnuOptimizer.active_i = self.optimizers.index(opt)
184 self.__optimizer = opt
186 for i, opt in enumerate(self.optimizers):
187 if opt.name == name:
188 self.__optimizer = opt
189 self.mnuOptimizer.active_i = i
190 self.__prefs['optimizer_name'].data = name
191 return
192 raise ValueError('No optimizer named %s' % repr(name))
193
196 traceback.print_exception(typ, val, tb)
197
198
199
200 @Propertied(Property('optimize', False, 'False to evaluate LL without optimizing'),
201 Property('model_group', 1, 'Group index of models to consider'))
203 __explore_featured = ['run']
205 qubx.faces.Face.__init__(self, 'Classify', 'QubX.Modeling.Utils.Classify')
206 self.propertied_connect_settings('Modeling.Utils.Classify')
207 line = pack_item(gtk.HBox(), self)
208 chkEval = pack_radio('evaluate or', line)
209 chkOpt = pack_radio('optimize', line, group=chkEval)
210 self.propertied_connect_radios('optimize', [(False, chkEval), (True, chkOpt)])
211 pack_label('the LL of each selection in the active List', line)
212 line = pack_item(gtk.HBox(), self)
213 pack_label('for each model in group', line)
214 txtGroup = pack_item(qubx.GTK.NumEntry(self.model_group, acceptIntGreaterThanOrEqualTo(0), width_chars=4), line)
215 self.propertied_connect_NumEntry('model_group', txtGroup)
216 line = pack_item(gtk.HBox(), self)
217 pack_label("Each selection's Group will be set to the Index of the highest-scoring model.", line)
218 line = pack_item(gtk.HBox(), self)
219 pack_space(line, expand=True)
220 pack_space(line, expand=True)
221 self.btnRun = pack_button('Run', line, self.__onClickRun)
222 pack_space(line, expand=True)
226 - def run(self, receiver=lambda: None, wait=True):
233
235 - def __init__(self, lst, optimize=False, model_group=0, receiver=lambda: None):
257 cancel()
258 self.__stop_flag = True
259 - def get_score(self, model_prep, data_prep, **kw):
263 if self.optimize:
264 bk = model_prep.source.as_tree()
265 result = self.optimizer.optimize(model_prep, data_prep, on_trial=on_trial, **kw)
266 model_prep.source.from_tree(bk)
267 else:
268 result = self.optimizer.get_ll(model_prep, data_prep, on_trial=on_trial, **kw)
269 return result
271 qubx.task.Tasks.add_task(self)
272 self.status = 'Running...'
273 try:
274 scores = []
275 for s, seg in enumerate(self.segs):
276 seg.Label = 'List[%i]' % s
277 progress = self.progress = s * 100.0 / len(self.segs)
278 row = []
279 scores.append(row)
280 for m, model_prep in enumerate(self.models_prep):
281 if self.__stop_flag:
282 raise KeyboardInterrupt
283 self.progress = progress + m * 100.0 / (len(self.segs) * len(self.models_prep))
284 data_prep = self.gui_call_recv(self.optimizer.get_data_prep, [seg], model_prep, wait=False, receiver=self)[0]
285 ll = self.gui_call_recv(self.get_score, model_prep, data_prep, wait=False, receiver=self)[0]
286 row.append(ll)
287 groups = [numpy.argmax(row) for row in scores] if (len(scores) and len(scores[0])) else []
288 gobject.idle_add(self.__set_groups, groups, scores)
289 finally:
290 qubx.global_namespace.QubX.Modeling.Utils.Classify.btnRun.set_sensitive(True)
291 gobject.idle_add(qubx.global_namespace.QubX.Modeling.Utils.request_show)
292 gobject.idle_add(self.receiver)
293 qubx.task.Tasks.remove_task(self)
295 l = self.lst
296 mdl_lbls = ['%s %s' % (self.optimizer.name, model_prep.source and os.path.split(model_prep.source.path)[1] or str(model_prep.Index))
297 for model_prep in self.models_prep]
298 for i, g in enumerate(groups):
299 l[i, 'Group'] = g
300 for j, score in enumerate(scores[i]):
301 l[i, mdl_lbls[j]] = score
302
303
304
305 @Propertied(Property('delta_ll', 1.0, 'minimum improvement in LL, or else it stops adding states'),
306 Property('hub_state', 0, 'Index of hub state (connected to added states)'))
308 __explore_featured = ['run']
310 qubx.faces.Face.__init__(self, 'Star', 'QubX.Modeling.Utils.Star')
311 self.propertied_connect_settings('Modeling.Utils.Star')
312 line = pack_item(gtk.HBox(), self)
313 pack_label("Repeatedly adds states, connected from hub state, until log-likelihood improves by less than delta LL", line)
314 line = pack_item(gtk.HBox(), self)
315 pack_label('Hub state:', line)
316 txtHub = pack_item(qubx.GTK.NumEntry(self.hub_state, acceptIntGreaterThanOrEqualTo(0), width_chars=3), line)
317 self.propertied_connect_NumEntry('hub_state', txtHub)
318 pack_label('Delta LL:', line)
319 txtDeltaLL = pack_item(qubx.GTK.NumEntry(self.delta_ll, acceptFloatNonzero, '%.3g', width_chars=5), line)
320 self.propertied_connect_NumEntry('delta_ll', txtDeltaLL)
321 pack_space(line, x=20)
322 self.btnRun = pack_button('Run', line, self.__onClickRun)
326 - def run(self, receiver=lambda: None, wait=True):
333
334 @Propertied(Property('delta_ll', 1.0, 'minimum improvement in LL, or else it stops adding states'))
335 -class ChainFace(qubx.faces.Face):
336 __explore_featured = ['run']
338 qubx.faces.Face.__init__(self, 'Chain', 'QubX.Modeling.Utils.Chain')
339 self.propertied_connect_settings('Modeling.Utils.Chain')
340 line = pack_item(gtk.HBox(), self)
341 pack_label("Repeatedly adds linear states until log-likelihood improves by less than delta LL", line)
342 line = pack_item(gtk.HBox(), self)
343 pack_label('Delta LL:', line)
344 txtDeltaLL = pack_item(qubx.GTK.NumEntry(self.delta_ll, acceptFloatNonzero, '%.3g', width_chars=5), line)
345 self.propertied_connect_NumEntry('delta_ll', txtDeltaLL)
346 pack_space(line, x=20)
347 self.btnRun = pack_button('Run', line, self.__onClickRun)
351 - def run(self, receiver=lambda: None, wait=True):
358
360 - def __init__(self, name, add_state, delta_ll, face, receiver=lambda: None):
370 cancel()
371 self.__stop_flag = True
386
387
388
389 -def build_styled_model(optimizer, add_state, optimize=True, delta_ll=1.0, data_prep=None, task=None, should_quit=lambda: False):
390 """Builds up a model by, for all extant classes, repeatedly adding a state until LL improves by less than delta_ll.
391 Can be called from non-gobject Task threads if you provide the L{qubx.task.Task}.
392
393 @param optimizer: L{Optimizer}
394 @param add_state: func(class_index) called on gobject thread, operates on QubX.Models.file
395 @param optimize: False to evaluate model LL as-is
396 @param data_prep: optional prepared data to override onscreen
397 @param task: L{qubx.task.Task} if called from Task/robot thread
398 """
399 QubX = qubx.global_namespace.QubX
400 model = QubX.Models.file
401 kept_tree = model.as_tree()
402 classes_present = sorted(list(set(state.Class for state in model.states)))
403 if task:
404 if optimize:
405 def get_score():
406 return task.gui_call_recv(optimizer.optimize, data_prep=data_prep, wait=False, receiver=task)[0]
407 else:
408 def get_score():
409 return task.gui_call_recv(optimizer.get_ll, data_prep=data_prep, wait=False, receiver=task)[0]
410 def gui_add_state(class_index):
411 task(add_state(class_index))
412 def call_add_state(class_index):
413 return task.gui_call_recv(gui_add_state, class_index)
414 def gui_rewind(tree):
415 task(model.from_tree(tree))
416 def rewind(tree):
417 task.gui_call_recv(gui_rewind, tree)
418 else:
419 if optimize:
420 def get_score():
421 return optimizer.optimize(data_prep=data_prep)
422 else:
423 def get_score():
424 return optimizer.get_ll(data_prep=data_prep)
425 def call_add_state(class_index):
426 return add_state(class_index)
427 rewind = model.from_tree
428
429 postLL = get_score()
430 preLL = postLL - 2 * delta_ll
431 for cls in classes_present:
432 while (postLL - preLL) > delta_ll:
433 if should_quit():
434 return
435 kept_tree = model.as_tree()
436 call_add_state(cls)
437 preLL, postLL = postLL, get_score()
438 rewind(kept_tree)
439 preLL = postLL - 2 * delta_ll
440 get_score()
441
445 ss = m.states
446 dx = ss[s2, 'x'] - ss[s1, 'x']
447 dy = ss[s2, 'y'] - ss[s1, 'y']
448 mag = sqrt(dx*dx + dy*dy)
449 angle = asin(dy/mag)
450 if dx < 0:
451 if dy >= 0:
452 angle = pi - angle
453 else:
454 angle = -pi - angle
455 return angle
456
458 def add_state(cls):
459 QubX = qubx.global_namespace.QubX
460 model = QubX.Models.file
461 states = model.states
462 hub_ix = min(hub_index, len(states)-1)
463
464 angles = [ (AngleOfStates(model, hub_ix, i), i) for i in xrange(states.size) if i != hub_ix ]
465 angles.sort()
466 angles.append( (angles[0][0] + 2*pi, angles[0][1]) )
467
468 anglediffs = [ (angles[i+1][0] - angles[i][0], angles[i][1], i) for i in range(len(angles) - 1) ]
469 anglediffs.sort()
470 insertafter = max(anglediffs)
471 insertaftersorted = insertafter[2]
472 abef, aaft = angles[ insertaftersorted ][0], angles[ insertaftersorted + 1 ][0]
473 insertangle = abef + (aaft - abef) / 2
474 if insertangle >= 2*pi:
475 insertangle = insertangle - 2*pi
476 x0, y0 = states[hub_ix, 'x'], states[hub_ix, 'y']
477 dx, dy = cos(insertangle), sin(insertangle)
478 if dx != 0:
479 xmag = max( (100 - x0) / dx, -x0 / dx )
480 else:
481 xmag = 1.0e10
482 if dy != 0:
483 ymag = max( (100 - y0) / dy, -y0 / dy )
484 else:
485 ymag = 1.0e10
486 mag = 0.8 * min( xmag, ymag )
487
488 states.append({'Class' : cls, 'x' : x0 + mag * dx, 'y' : y0 + mag * dy})
489 model.rates.append({'From' : hub_ix, 'To' : len(states)-1})
490 model.rates.append({'To' : hub_ix, 'From' : len(states)-1})
491 return add_state
492
496 QubX = qubx.global_namespace.QubX
497 model = QubX.Models.file
498 rates = model.rates
499 states = model.states
500
501 prev, statesOfCls, post = SubChain(model, cls)
502
503 s = len(states)
504 if len(statesOfCls) == 1 and prev is None:
505 states.append({'Class' : cls, 'x' : 5, 'y' : states[0, 'y']})
506 rates.append({'From' : statesOfCls[0], 'To' : s})
507 rates.append({'To' : statesOfCls[0], 'From' : s})
508 elif len(statesOfCls) == 1 and post is None:
509 states.append({'Class' : cls, 'x' : 95, 'y' : states[0, 'y']})
510 rates.append({'From' : statesOfCls[0], 'To' : s})
511 rates.append({'To' : statesOfCls[0], 'From' : s})
512 else:
513 if post: statesOfCls.append(post)
514 gaps = [ (states[statesOfCls[i+1], 'x'] - states[statesOfCls[i], 'x'], i) for i in range(len(statesOfCls)-1) ]
515 gaps.sort()
516 after = gaps[ len(gaps) - 1 ][1]
517 lh, rh = statesOfCls[after], statesOfCls[after+1]
518 states.append({'Class' : cls, 'x' : (states[lh,'x'] + states[rh,'x'])/2, 'y' : (states[lh,'y'] + states[rh,'y'])/2})
519
520 rf = rb = None
521 for r in xrange(rates.size):
522 if (rates[r,'From'] == lh) and (rates[r,'To'] == rh):
523 rf = rates.get_row(r)
524 elif (rates[r, 'To'] == lh) and (rates[r,'From'] == rh):
525 rb = rates.get_row(r)
526 for r_del in reversed(sorted([x.Index for x in (rf, rb) if not (x is None)])):
527 rates.remove(r_del)
528
529 k0f = 10.0 if (rf is None) else (rf.k0 / 2)
530 k0b = 10.0 if (rb is None) else (rb.k0 / 2)
531 Lf = '' if (rf is None) else rf.Ligand
532 Lb = '' if (rb is None) else rb.Ligand
533 Vf = '' if (rf is None) else rf.Voltage
534 Vb = '' if (rb is None) else rb.Voltage
535 Pf = '' if (rf is None) else rf.Pressure
536 Pb = '' if (rb is None) else rb.Pressure
537 if rf is None:
538 k1f = .001 if Vf else 0.0
539 k2f = 1.0 if Pf else 0.0
540 else:
541 k1f = rf.k1 / 2
542 k2f = rf.k2 / 2
543 if rb is None:
544 k1b = .001 if Vb else 0.0
545 k2b = 1.0 if Pb else 0.0
546 else:
547 k1b = rb.k1 / 2
548 k2b = rb.k2 / 2
549 rates.append({'From' : lh, 'To' : s, 'k0' : k0f, 'k1' : k1f, 'k2' : k2f, 'Ligand' : Lf, 'Voltage' : Vf, 'Pressure' : Pf})
550 rates.append({'From' : s, 'To' : lh, 'k0' : k0b, 'k1' : k1b, 'k2' : k2b, 'Ligand' : Lb, 'Voltage' : Vb, 'Pressure' : Pb})
551 rates.append({'From' : rh, 'To' : s, 'k0' : k0b, 'k1' : k1b, 'k2' : k2b, 'Ligand' : Lb, 'Voltage' : Vb, 'Pressure' : Pb})
552 rates.append({'From' : s, 'To' : rh, 'k0' : k0f, 'k1' : k1f, 'k2' : k2f, 'Ligand' : Lf, 'Voltage' : Vf, 'Pressure' : Pf})
553
555 connections = collections.defaultdict(lambda: [])
556 for rate in model.rates:
557 connections[ rate.From ].append( rate.To )
558 return connections
559
562
574
576 order = ChainStateOrder(model)
577 subchain = [(order[i], i) for i in range(len(order)) if model.states[order[i], 'Class'] == cls]
578 prev = None
579 post = None
580
581 ixInOrder = subchain[0][1]
582 if ixInOrder > 0:
583 prev = order[ ixInOrder - 1 ]
584 ixInOrder = subchain[ len(subchain) - 1 ][1]
585 if ixInOrder < (len(order) - 1):
586 post = order[ ixInOrder + 1 ]
587
588 subchain = [item[0] for item in subchain]
589 return prev, subchain, post
590
591
592
593
594
595 @Propertied(Property('trials_per_model', 3, 'Number of times to optimize each model'),
596 Property('no_loops', True, 'True to discard models with cycles'),
597 Property('balance_loops', True, 'True to add detailed balance constraints to all cycles'),
598 Property('max_connections', 10, 'Discards models with more connections between states'),
599 Property('max_exit_rates', 5, 'Discards models with excess connections to/from a single state'),
600 Property('initial_k0', 100.0, 'Starting pre-exponential rate constant'),
601 Property('random_rates', True, 'True to scale starting rates randomly from initial_k0'),
602 Property('random_from', 0.1, 'Minimum random scaling of starting rates'),
603 Property('random_to', 5.0, 'Maximum random scaling of starting rates'),
604 Property('output_name', 'Model Search', 'Name of output "Trials" table'),
605 Property('max_output', 0, 'If nonzero, sorts by AIC (or LL if AIC is 0), and outputs only the top entries.'),
606 Property('decoration_script', """# This script can adjust each model before optimizing.
607 # Work on search_model. search_number is the nauty class. For examples, turn on script recording (Admin:Scripts panel).
608
609 # e.g. adding pressure-dependence to opening rates:
610 #rates, states = search_model.rates, search_model.states
611 #for rate in rates: # rate is read-only; write to rates[index, field]
612 # if states[rate.From, 'Class'] < states[rate.To, 'Class']:
613 # rates[rate.Index, 'Pressure'] = "Pressure"
614 # rates[rate.Index, 'k2'] = 0.01
615 """, 'Script which is run for each trial, before optimizing'),
616 Property('post_script', """# This script runs after optimizing. Work on search_model.
617 """, 'Script which is run for each trial, after optimizing', in_tree=False))
619 __explore_featured = ['model_lists', 'txtScript', 'deferred_update', 'list_models', 'run']
621 qubx.faces.Face.__init__(self, 'ModelSearch', 'QubX.Modeling.Utils.ModelSearch')
622 self.__ref = Reffer()
623 self.model_lists = qubx.tree.Open(os.path.join(qubx.global_namespace.app_path, 'msearch-models.qtr'), True)
624 self.model_lists.close()
625 self.propertied_connect_settings('Modeling.Utils.ModelSearch')
626 self.left_right = pack_item(gtk.HPaned(), self)
627 self.panOptions = gtk.HBox()
628 self.panOptions.show()
629 self.left_right.pack1(self.panOptions, False, True)
630 self.panOptions1 = pack_item(gtk.VBox(), self.panOptions, expand=True)
631 line = pack_item(gtk.HBox(True), self.panOptions1)
632 pack_label('Trials per model:', line)
633 self.txtTrialsPerModel = pack_item(qubx.GTK.NumEntry(self.trials_per_model, acceptIntGreaterThan(0), width_chars=6), line)
634 self.propertied_connect_NumEntry('trials_per_model', self.txtTrialsPerModel)
635 self.chkNoLoops = pack_check('No loops', self.panOptions1)
636 self.propertied_connect_check('no_loops', self.chkNoLoops)
637 self.chkBalanceLoops = pack_check('Balance loops', self.panOptions1)
638 self.propertied_connect_check('balance_loops', self.chkBalanceLoops)
639 line = pack_item(gtk.HBox(True), self.panOptions1)
640 pack_label('Max connections:', line)
641 self.txtMaxConnections = pack_item(qubx.GTK.NumEntry(self.max_connections, acceptIntGreaterThan(1), width_chars=6), line)
642 self.propertied_connect_NumEntry('max_connections', self.txtMaxConnections)
643 line = pack_item(gtk.HBox(), self.panOptions1)
644 pack_label('Max exit rates per state:', line)
645 line = pack_item(gtk.HBox(True), self.panOptions1)
646 pack_item(gtk.EventBox(), line)
647 self.txtMaxExitRates = pack_item(qubx.GTK.NumEntry(self.max_exit_rates, acceptIntGreaterThan(0), width_chars=6), line)
648 self.propertied_connect_NumEntry('max_exit_rates', self.txtMaxExitRates)
649
650 self.panOptions2 = pack_item(gtk.VBox(), self.panOptions, expand=True)
651 line = pack_item(gtk.HBox(True), self.panOptions2)
652 pack_label('Initial k0:', line)
653 self.txtInitialK0 = pack_item(qubx.GTK.NumEntry(self.initial_k0, acceptFloatGreaterThan(0.0), '%.5g', width_chars=6), line)
654 self.propertied_connect_NumEntry('initial_k0', self.txtInitialK0)
655 self.chkRandomRates = pack_check('Randomize starting rates', self.panOptions2)
656 self.propertied_connect_check('random_rates', self.chkRandomRates)
657 line = pack_item(gtk.HBox(True), self.panOptions2)
658 pack_label(' from:', line)
659 self.txtRandomFrom = pack_item(qubx.GTK.NumEntry(self.random_from, acceptFloatBetween(1e-5, 1.0), '%.5g', width_chars=6), line)
660 self.propertied_connect_NumEntry('random_from', self.txtRandomFrom)
661 line = pack_item(gtk.HBox(True), self.panOptions2)
662 pack_label(' to:', line)
663 self.txtRandomTo = pack_item(qubx.GTK.NumEntry(self.random_to, acceptFloatGreaterThan(1.0), '%.5g', width_chars=6), line)
664 self.propertied_connect_NumEntry('random_to', self.txtRandomTo)
665 pack_label('times initial k0', self.panOptions2)
666 line = pack_item(gtk.HBox(), self.panOptions2)
667 self.lblModelCount = pack_label('', line)
668
669 self.panScript = gtk.VBox()
670 self.panScript.show()
671 self.left_right.pack2(self.panScript, True, True)
672 lbl = pack_label('Per-trial model decoration script:', self.panScript)
673
674 self.txtScript = gtk.TextView()
675 pack_scrolled(self.txtScript, self.panScript, size_request=(100, 40), expand=True)
676 self.txtScript.connect('focus_out_event', self.__onExitScript)
677 qubx.GTK.SetFixedWidth(self.txtScript)
678 self.txtScript.get_buffer().set_text(self.decoration_script)
679
680 line = pack_item(gtk.HBox(), self.panScript, at_end=True)
681 pack_label('Output Trials name:', line)
682 self.txtOutputName = pack_item(qubx.GTK.NumEntry(self.output_name), line, expand=True)
683 self.propertied_connect_NumEntry('output_name', self.txtOutputName)
684 self.mnuPresets = pack_item(qubx.settingsGTK.PresetsMenu('Modeling.Utils.ModelSearch', qubx.global_namespace.QubX.appname), line)
685 self.btnRun = pack_button('Run Search', line, self.__onClickRun)
686
687 QubX = qubx.global_namespace.QubX
688 self.deferred_update = qubx.pyenv.DeferredAction(delay_ms=5)
689 QubX.Models.OnSwitch += self.__ref(self.__onSwitchModel)
690 self.__model = None
691 self.__onSwitchModel(QubX.Models, QubX.Models.file)
692
728 - def list_models(self, file=None, no_loops=None, max_connections=None, max_exit_rates=None):
747 - def run(self, receiver=lambda: None, wait=True):
765 else:
766 decorate = None
767 try:
768 postor.preload(self.post_script)
769 except:
770 qubx.pyenvGTK.show_message(traceback.format_exc(), title='Model Search: Post script error', parent=self.parent_window)
771 receiver()
772 return
773 if postor:
774 def post_script(model):
775 qubx.global_namespace.search_model = model
776 postor(raise_exceptions=True)
777 else:
778 post_script = None
779
780
781 if wait:
782 return qubx.pyenv.call_async_wait(self.run, wait=False)
783 self.btnRun.set_sensitive(False)
784 task = ModelSearchTask(self.__model, models, self.trials_per_model, self.balance_loops, self.initial_k0,
785 self.random_rates, self.random_from, self.random_to, decorate, post_script, self.output_name, receiver)
786 task.OnException += task_exception_to_console
787 task.start()
788
790 clazz = [model.states[i, 'Class'] for i in xrange(len(model.states))]
791 class_size = [0] * (max(clazz) + 1)
792 for c in clazz:
793 class_size[c] += 1
794 class_order = []
795 for n in reversed(xrange(1, len(clazz))):
796 if n in class_size:
797 for i, nc in enumerate(class_size):
798 if nc == n:
799 class_order.append(i)
800 nauty_class_chars = ['N', str(len(clazz))]
801 state_order = []
802 for i, sel_cls in enumerate(class_order):
803 for j, c in enumerate(clazz):
804 if c == sel_cls:
805 nauty_class_chars.append(str(i+1))
806 state_order.append(j)
807 return ''.join(nauty_class_chars), state_order
808
810 counts = [0] * nstate
811 for i, conn in enumerate(nauty_connections(nstate, code)):
812 if i == max_conn:
813 return False
814 a, b = conn
815 counts[a] += 1
816 if counts[a] > max_exit:
817 return False
818 counts[b] += 1
819 if counts[b] > max_exit:
820 return False
821 return True
822
823 -def nauty_setup_model(model, state_order, code, balance_loops, initial_k0, random_rates, random_from, random_to, decorate):
824 model.rates.clear()
825 for a, b in nauty_connections(model.states.size, code):
826 model.rates.append({'From' : state_order[a], 'To' : state_order[b],
827 'k0' : random_rates and random_scaled(initial_k0, random_from, random_to) or initial_k0})
828 model.rates.append({'To' : state_order[a], 'From' : state_order[b],
829 'k0' : random_rates and random_scaled(initial_k0, random_from, random_to) or initial_k0})
830 if balance_loops:
831 model.balance_all_loops()
832 if decorate:
833 try:
834 decorate(model, code)
835 except:
836 qubx.pyenvGTK.show_message(traceback.format_exc(), title='Error in ModelSearch decoration script')
837
840 maxpath = nstate * (nstate - 1) / 2
841 pathsel = 1 << (maxpath - 1)
842 for a in xrange(nstate):
843 for b in xrange(a+1, nstate):
844 if code & pathsel:
845 yield(a, b)
846 pathsel >>= 1
847
849 return center * exp(random.uniform(log(lo_mult), log(hi_mult)))
850
854 - def __init__(self, model, models, trials_per_model, balance_loops, initial_k0, random_rates, random_from, random_to, decorate, post_script, output_name, receiver=lambda: None):
876 nauty_setup_model(self.model, self.models.state_order, code, self.balance_loops, self.initial_k0,
877 self.random_rates, self.random_from, self.random_to, self.decorate)
878 self.trial_label = label
879 self.optimizer.optimize(self.optimizer.get_model_prep(self.model), wait=False, receiver=receiver, on_trial=self.__onTrial)
891 cancel()
892 self.__stop_flag = True
894 if self.trials_per_model > 1:
895 trial_label = lambda code, t: "%i-%i" % (code, t+1)
896 else:
897 trial_label = lambda code, t: code
898 qubx.task.Tasks.add_task(self)
899 self.status = 'Running...'
900 try:
901 for i, code in enumerate(self.models.nauty_codes):
902 for t in xrange(self.trials_per_model):
903 if self.__stop_flag:
904 raise KeyboardInterrupt
905 self.gui_call_recv(self.start_trial, code, trial_label(code, t), receiver=self)
906 self.gui_call_recv(self.end_trial, code, trial_label(code, t), receiver=self)
907 self.progress = (i*self.trials_per_model + t) * 100.0 / (len(self.models.nauty_codes * self.trials_per_model))
908 finally:
909 QubX = qubx.global_namespace.QubX
910 QubX.Modeling.Utils.ModelSearch.btnRun.set_sensitive(True)
911 gobject.idle_add(QubX.Modeling.Utils.request_show)
912 gobject.idle_add(self.receiver)
913 gobject.idle_add(lambda: QubX.Tables.show_table(QubX.Tables.find_table('Trials')))
914 qubx.task.Tasks.remove_task(self)
915
916
917
918 @Propertied(Property('optimize', False, 'False to evaluate LL without optimizing'),
919 Property('all_at_once', False, 'False to perturb and evaluate parameters one at a time, into the Trials table'),
920 Property('perturb_rates', True, ''),
921 Property('perturb_channelCount', False, ''),
922 Property('perturb_amp', False, ''),
923 Property('perturb_std', False, ''),
924 Property('perturb_cond', False, ''),
925 Property('perturb_condstd', False, ''),
926 Property('perturb_pct', 10.0, 'Perturbed parameter in gaussian(mean=original, std=original * (pct/100))'),
927 Property('trials', 1, 'Number of repeats'))
929 __explore_featured = ['run']
931 qubx.faces.Face.__init__(self, 'Perturb', 'QubX.Modeling.Utils.Perturb')
932 self.propertied_connect_settings('Modeling.Utils.Perturb')
933 columns = pack_item(gtk.HBox(), self, expand=True)
934 col = pack_item(gtk.VBox(), columns)
935 pack_label('Adjust', col)
936 col = pack_item(gtk.VBox(), columns)
937 self.chkEach = pack_radio('each', col)
938 self.chkAll = pack_radio('all at once', col, group=self.chkEach)
939 self.propertied_connect_radios('all_at_once', [(False, self.chkEach), (True, self.chkAll)])
940 col = pack_item(gtk.VBox(), columns)
941 self.chkPerturbRates = pack_check('rate constants', col)
942 self.propertied_connect_check('perturb_rates', self.chkPerturbRates)
943 self.chkPerturbChannelCount = pack_check('channel count', col)
944 self.propertied_connect_check('perturb_channelCount', self.chkPerturbChannelCount)
945 self.chkPerturbAmp = pack_check('Classes:Amp', col)
946 self.propertied_connect_check('perturb_amp', self.chkPerturbAmp)
947 self.chkPerturbStd = pack_check('Classes:Std', col)
948 self.propertied_connect_check('perturb_std', self.chkPerturbStd)
949 self.chkPerturbCond = pack_check('Classes:Cond', col)
950 self.propertied_connect_check('perturb_cond', self.chkPerturbCond)
951 self.chkPerturbCondStd = pack_check('Classes:CondStd', col)
952 self.propertied_connect_check('perturb_condstd', self.chkPerturbCondStd)
953 col = pack_item(gtk.VBox(), columns)
954 line = pack_item(gtk.HBox(), col)
955 pack_label(' randomly by ', line)
956 self.txtPct = pack_item(qubx.GTK.NumEntry(self.perturb_pct, acceptFloatGreaterThan(0.0), '%.0f', width_chars=4), line)
957 self.propertied_connect_NumEntry('perturb_pct', self.txtPct)
958 pack_label('%, then ', line)
959 col = pack_item(gtk.VBox(), columns)
960 chkEval = pack_radio('evaluate or', col)
961 chkOpt = pack_radio('optimize', col, group=chkEval)
962 self.propertied_connect_radios('optimize', [(False, chkEval), (True, chkOpt)])
963 self.btnRun = pack_button('Run', col, self.__onClickRun, at_end=True)
964 line = pack_item(gtk.HBox(), col, at_end=True)
965 pack_label('Trials:', line)
966 self.txtTrials = pack_item(qubx.GTK.NumEntry(self.trials, acceptIntGreaterThan(0), width_chars=2), line, expand=True)
967 self.propertied_connect_NumEntry('trials', self.txtTrials)
971 - def run(self, receiver=lambda: None, wait=True):
972 if wait:
973 return qubx.pyenv.call_async_wait(self.run, wait=False)
974 self.btnRun.set_sensitive(False)
975 task = PerturbTask(self.optimize, gen_perturbations(qubx.global_namespace.QubX.Models.file,
976 self.all_at_once, self.perturb_rates, self.perturb_channelCount,
977 self.perturb_amp, self.perturb_std, self.perturb_cond, self.perturb_condstd,
978 self.perturb_pct, self.trials),
979 receiver)
980 task.OnException += task_exception_to_console
981 task.start()
982
983 -def gen_perturbations(model, all_at_once, perturb_rates, perturb_channelCount, perturb_amp, perturb_std, perturb_cond, perturb_condstd,
984 perturb_pct, trials):
985 orig_tree = model.as_tree()
986 yield ('Initial', model)
987 def perturb(x):
988 dev_pct = random.gauss(0, perturb_pct)
989 return (x + (dev_pct / 100.0) * x, dev_pct)
990
991 rates = model.rates
992 classes = model.classes
993 states = model.states
994 classes_used = set(states[i, 'Class'] for i in xrange(len(states)))
995 for t in xrange(1, trials+1):
996 if all_at_once:
997 model.from_tree(orig_tree)
998 if perturb_rates:
999 for rate in rates:
1000 if not all_at_once:
1001 model.from_tree(orig_tree)
1002 rates[rate.Index, 'k0'], dev_pct = perturb(rate.k0)
1003 if not all_at_once:
1004 yield ("k0 %i->%i + %.1f%% (%i)" % (rate.From, rate.To, dev_pct, t), model)
1005 if rate.k1 and rate.Voltage:
1006 if not all_at_once:
1007 model.from_tree(orig_tree)
1008 rates[rate.Index, 'k1'], dev_pct = perturb(rate.k1)
1009 if not all_at_once:
1010 yield ("k1 %i->%i + %.1f%% (%i)" % (rate.From, rate.To, dev_pct, t), model)
1011 if rate.k2 and rate.Pressure:
1012 if not all_at_once:
1013 model.from_tree(orig_tree)
1014 rates[rate.Index, 'k2'], dev_pct = perturb(rate.k2)
1015 if not all_at_once:
1016 yield ("k2 %i->%i + %.1f%% (%i)" % (rate.From, rate.To, dev_pct, t), model)
1017 if perturb_channelCount:
1018 if not all_at_once:
1019 model.from_tree(orig_tree)
1020 cc, dev_pct = perturb(model.channelCount)
1021 model.channelCount = max(1, int(round(cc)))
1022 if not all_at_once:
1023 yield ("Channel count + %.1f%% (%i)" % (dev_pct, t), model)
1024 for flag, field in [(perturb_amp, 'Amp'), (perturb_std, 'Std'), (perturb_cond, 'Cond'), (perturb_condstd, 'CondStd')]:
1025 if flag:
1026 for c in sorted(classes_used):
1027 if not all_at_once:
1028 model.from_tree(orig_tree)
1029 classes[c, field], dev_pct = perturb(classes[c, field])
1030 if not all_at_once:
1031 yield ("%s[%i] + %.1f%% (%i)" % (field, c, dev_pct, t), model)
1032 if all_at_once:
1033 yield ('%i'%t, model)
1034
1036 - def __init__(self, optimize, lbl_models, receiver=lambda: None):
1050 cancel()
1051 self.__stop_flag = True
1056 try:
1057 label, model = self.lbl_models.next()
1058 if (not self.__first_trial) and self.optimize and not self.__first_trial:
1059 action = self.optimizer.optimize
1060 else:
1061 action = self.optimizer.get_ll
1062 self.__first_trial = False
1063 action(self.optimizer.get_model_prep(model), on_trial=on_trial, **kw)
1064 return True
1065 except StopIteration:
1066 return False
1067 except:
1068 traceback.print_exc()
1069 return False
1082