1 """Provides a visual panel (L{qubx.faces.Face}) to optimize rate constants from ensemble measurement.
2
3 Uses math from U{qubx_macroscopic<http://www.qub.buffalo.edu/src/qub-express/api/qubx_macroscopic>}
4
5 /* Copyright 2008-2014 Research Foundation State University of New York */
6
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 cStringIO
26 import itertools
27 import gc
28 import operator
29 import os
30 import sys
31 import time
32 import traceback
33
34 from qubx.data_types import *
35 from qubx.util_types import *
36 from qubx.faces import *
37 from qubx.GTK import *
38 import qubx.fast.data
39 import qubx.fast.model
40 import qubx.notebook
41 import qubx.optimize
42 import qubx.optimizeGTK
43 import qubx.pyenv
44 import qubx.tree
45 from qubx.model import *
46 from qubx.task import *
47 from qubx.settings import Propertied, Property
48 from qubx.trialset import add_trial_rates, add_trial_amps, add_trial_nChannel
49 import max_mac_ll
50 from . import qubx_plugin
51
52 import scipy.optimize
53 from numpy import *
54
55 import ctypes
56 from ctypes import c_int, c_float, c_double, c_void_p, c_char_p, Structure, POINTER, CFUNCTYPE, byref, sizeof, py_object, pointer, cast
57 pybuf = ctypes.pythonapi.PyBuffer_FromReadWriteMemory
58 pybuf.restype = py_object
59
60
61 MIN_SECONDS_BETWEEN_FIT = 3
62 MAXITER = 300
65 """Represents data ready for computation."""
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 @Propertied(Property('use_Peq', True, "True to start each segment from equilibrium; False to use States:Pr"),
85 Property('use_group', False, "True to fit a group of data files", in_tree=False),
86 Property('use_group_index', 1, "Group of interest in Data table"),
87 Property('resample', False, "True to resample by delta y"),
88 Property('resample_delta', 0.5, 'an interval with std < delta is resampled as one data point'),
89 Property('Gleak', 0.0, 'Leakage conductance (pS)'),
90 Property('Vleak', 0.0, 'Leak voltage (mV)'),
91 Property('opt_Nchannel', False, "True to optimize the model's channel count"),
92 Property('opt_cond', False, "True to optimize the model's Amps/Conds"),
93 Property('opt_var', False, "True to optimize the model's Stds/CondStds"),
94
95 Property('live', False, "True to redo fit curve when model changes", in_tree=False),
96 Property('use_GPU', False, "True to use OpenCL-capable graphics card, if avail", in_tree=False),
97 Property('multi_model', False, "True to fit the sum of a group of models", in_tree=False),
98 Property('multi_model_group', 1, "Group for multi_model fitting (column in Models table)")
99 )
101 """Calculates log-likelihood and optimizes model parameters, a la Milescu 2005.
102
103 @ivar OnIteration: L{WeakEvent}(iteration, LL, grads, K0, K1, amp, std, amp0, std0, Nchannel)
104 @ivar OnIteration2: L{WeakEvent}(iteration, LL, grads, K0, K1, K2, amp, std, amp0, std0, Nchannel)
105 @ivar OnOptimized: L{WeakEvent}(iterations, LL, grads, Hessian)
106 @ivar OnLiveLL: L{WeakEvent}(LL)
107 """
108
109 __explore_featured = ['accel', 'rates', 'iterations', 'LL', 'last_LL', 'grads', 'OnLiveLL', 'fitbot', 'get_ndata',
110 'add_trial_fields', 'wait_for_fits']
111
112 - def __init__(self, name='MacRates', global_name='QubX.Modeling.MacRates'):
113 super(RatesFace, self).__init__(name, global_name,
114 "qubx_mac_rates_optimize",
115 {"max step" : 0.1,
116 "num grad dx" : 1e-4,
117 "conv ll" : 1e-3,
118 "conv grad" : 1e-3})
119 self.__ref = Reffer()
120 self.__invalid_score = True
121 self.__invalid_live = True
122 self.accel = 1
123 self.QubX = qubx.pyenv.env.globals['QubX']
124 self.QubX.OnQuit += self.__ref(self.mac_finalize)
125 self.propertied_connect_settings('MacRates')
126 self.data.use_group = self.use_group_index if self.use_group else None
127 self.rates = Product(self.__get_rates)
128 self.rates.OnInvalidate += self.__ref(self.__onInvalidateRates)
129 self.OnIteration = WeakEvent()
130 self.OnIteration2 = WeakEvent()
131 self.OnOptimized = WeakEvent()
132
133 self.robot.working = False
134 self.robot.workspaces = None
135 self.robot.optNchannel = self.robot.optCond = self.robot.optVar = False
136 self.iterations = 0
137 self.LL = self.last_LL = 0.0
138 self.grads = []
139 self.__Ndata = 0
140 self.OnLiveLL = WeakEvent()
141
142
143
144
145 self.fitbot = Robot('Apply Fit')
146 self.fitbot.OnException += self.__ref(self.on_exception)
147 self.fitbot.serial = self.fitbot.last_serial = 0
148
149 self.__last_fits_time = datetime.datetime.now()
150
151 if self.__last_fits_time.hour:
152 self.__last_fits_time = self.__last_fits_time.replace(hour=self.__last_fits_time.hour-1)
153
154
155
156 qubx.notebook.Notebook.register_auto('MacRates_eval.Messages', 'Messages, on MacRates: Get LL', False)
157 qubx.notebook.Notebook.register_auto('MacRates_eval.Model.Picture', 'Model picture, on MacRates: Get LL', False)
158 qubx.notebook.Notebook.register_auto('MacRates_eval.Model.PictureNO', 'Model picture, no overlays, on MacRates: Get LL', False)
159 qubx.notebook.Notebook.register_auto('MacRates_eval.Data.Picture', 'Data picture, on MacRates: Get LL', False)
160 qubx.notebook.Notebook.register_auto('MacRates_eval.Model.Classes', 'Classes table, on MacRates: Get LL', False)
161 qubx.notebook.Notebook.register_auto('MacRates_eval.Model.Rates', 'Rates table, on MacRates: Get LL', False)
162
163 qubx.notebook.Notebook.register_auto('MacRates_opt.Iterations', 'Iteration log, on MacRates: Optimize', True)
164 qubx.notebook.Notebook.register_auto('MacRates_opt.Messages', 'Messages, on MacRates: Optimize', True)
165 qubx.notebook.Notebook.register_auto('MacRates_opt.Model.Picture', 'Model picture, on MacRates: Optimize', True)
166 qubx.notebook.Notebook.register_auto('MacRates_opt.Model.PictureNO', 'Model picture, no overlays, on MacRates: Optimize', False)
167 qubx.notebook.Notebook.register_auto('MacRates_opt.Data.Picture', 'Data picture, on MacRates: Optimize', True)
168 qubx.notebook.Notebook.register_auto('MacRates_opt.Model.Classes', 'Classes table, on MacRates: Optimize', True)
169 qubx.notebook.Notebook.register_auto('MacRates_opt.Model.Rates', 'Rates table, on MacRates: Optimize', False)
170 qubx.notebook.Notebook.register_auto('MacRates_opt.Model.Constraints', 'Kinetic Constraints table, on MacRates: Optimize', True)
171
172 col = pack_item(gtk.VBox(), self.top_panel)
173 self.chkPeq = pack_check('start at equilibrium p', col)
174 self.chkPeq.set_tooltip_text('otherwise use States:Pr')
175 self.propertied_connect_check('use_Peq', self.chkPeq)
176 h = pack_item(gtk.HBox(), col)
177 self.chkResample = pack_check('resample by delta:', h)
178 self.chkResample.set_tooltip_text('skip slow changes:\ncompress data with std.dev < delta into a single point')
179 self.propertied_connect_check('resample', self.chkResample)
180 self.txtResample = pack_item(NumEntry(self.resample_delta, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h)
181 self.propertied_connect_NumEntry('resample_delta', self.txtResample)
182
183 pack_space(self.top_panel, x=20)
184 col = pack_item(gtk.VBox(), self.top_panel)
185 h = pack_item(gtk.HBox(), col)
186 pack_label('Gleak:', h)
187 self.txtGleak = pack_item(NumEntry(self.Gleak, acceptFloat, '%.3g', width_chars=6), h, at_end=True)
188 self.propertied_connect_NumEntry('Gleak', self.txtGleak)
189 h = pack_item(gtk.HBox(), col)
190 pack_label('Vleak:', h)
191 self.txtVleak = pack_item(NumEntry(self.Vleak, acceptFloat, '%.3g', width_chars=6), h, at_end=True)
192 self.propertied_connect_NumEntry('Vleak', self.txtVleak)
193
194 pack_space(self.top_panel, expand=True)
195 col = pack_item(gtk.VBox(True), self.top_panel)
196 lbl_d = pack_label('Multi-data:', col)
197 lbl_m = pack_label('Multi-model:', col)
198 col = pack_item(gtk.VBox(True), self.top_panel)
199 h = pack_item(gtk.HBox(), col)
200 self.chkUseGroup = pack_check('Group', h)
201 self.propertied_connect_check('use_group', self.chkUseGroup)
202 self.txtUseGroup = pack_item(qubx.GTK.NumEntry(self.use_group_index, acceptIntGreaterThan(-1), width_chars=3), h)
203 self.txtUseGroup.set_sensitive(self.use_group)
204 self.propertied_connect_NumEntry('use_group_index', self.txtUseGroup)
205 for widget in (lbl_d, self.chkUseGroup, self.txtUseGroup):
206 widget.set_tooltip_text('To select data files, set their Group number in the Data table.\nThey will be fit to common parameters')
207 h = pack_item(gtk.HBox(), col)
208 self.chkModelsGroup = pack_check('Group', h)
209 self.propertied_connect_check('multi_model', self.chkModelsGroup)
210 self.txtModelsGroup = pack_item(qubx.GTK.NumEntry(self.multi_model_group, acceptIntGreaterThan(-1), width_chars=3), h)
211 self.txtModelsGroup.set_sensitive(self.multi_model)
212 self.propertied_connect_NumEntry('multi_model_group', self.txtModelsGroup)
213 for widget in (lbl_m, self.chkModelsGroup, self.txtModelsGroup):
214 widget.set_tooltip_text("To select models, set their Group number in the Models table.\nData will be fit to the sum of all models' currents.")
215
216 pack_space(self.top_panel, expand=True)
217 col = pack_item(gtk.VBox(True), self.top_panel)
218 self.chkLive = pack_check('live ', col)
219 self.chkLive.set_tooltip_text('update LL and fit curve when the model changes')
220 self.propertied_connect_check('live', self.chkLive)
221 self.chkGPU = pack_check('use GPU ', col)
222 self.chkGPU.set_tooltip_text('use graphics card if available; faster but less accurate')
223 self.propertied_connect_check('use_GPU', self.chkGPU)
224
225 self.lblScore.set_text('LL:')
226 self.btnGetScore.set_label('Get LL')
227
228 pack_label('Optimize:', self.right_panel)
229 self.chkOptNchannel = pack_check('channel count', self.right_panel)
230 self.propertied_connect_check('opt_Nchannel', self.chkOptNchannel)
231 self.chkOptCond = pack_check('conductance', self.right_panel)
232 self.propertied_connect_check('opt_cond', self.chkOptCond)
233 self.chkOptVar = pack_check('variance', self.right_panel)
234 self.propertied_connect_check('opt_var', self.chkOptVar)
235 self.chkOptRates = pack_check('rates', self.right_panel, active=True)
236 self.chkOptRates.set_tooltip_text('To hold a rate constant, right-click it, "Add Constraint...," "Fix..."')
237 self.chkOptRates.set_sensitive(False)
238
239 self.outStatus = TextViewAppender(self.txtStatus)
240 self.outStatus.write('Based on "Maximum liklihood estimation of ion channel kinetics from macroscopic currents," Milescu LS, Akk G, Sachs F, Biophys J. 2005 Apr; 88(4):2494-515.\n')
241 self.outStatus.OnWrite += self.__ref(self.__onWriteStatus)
242 self.__nb_iter_buf = None
243
244 self.modelgroup = ModelGroup(self.multi_model_group)
245 self.modelgroup.OnChange += self.__ref(self.__onChangeModelGroup)
246 self.modelgroup.OnChangeRates += self.__ref(self.__onChangeModelGroupRates)
248 """Stops the worker threads; call this in qubx_plugin_fini."""
249 self.robot.stop()
250 self.fitbot.stop()
251 self.data = self.data_prep = None
253 traceback.print_exception(typ, val, trace)
254 traceback.print_exception(typ, val, trace, file=self.outStatus)
256 if not (self.__nb_iter_buf is None):
257 self.__nb_iter_buf.write(msg)
259 if (name, value) == ('use_group_index', None):
260 self.use_group = False
261 return
262 if (name, value) == ('multi_model_group', None):
263 self.multi_model = False
264 return
265 super(RatesFace, self).propertied_set(value, name)
266 if name in ('use_Peq', 'Gleak', 'Vleak'):
267 self.invalidate_controls()
268 if name in ('resample', 'resample_delta'):
269 self.data.invalidate()
270 if name in ('use_group', 'use_group_index'):
271 self.data.use_group = self.use_group_index if self.use_group else None
272 self.txtUseGroup.set_sensitive(self.use_group)
273 if (name == 'use_group_index') and not self.use_group:
274 self.use_group = True
275 if name == 'multi_model':
276 self.txtModelsGroup.set_sensitive(self.multi_model)
277 self.model_prep.invalidate()
278 self.rates.invalidate()
279 if (name == 'multi_model_group'):
280 if not self.multi_model:
281 self.multi_model = True
282 self.modelgroup.group = value
283 if name == 'live':
284 if value:
285 self.invalidate_controls()
286 if name == 'use_GPU':
287 self.invalidate_controls()
291 """Changes LL text color gray (outdated) or black (up-to-date), and starts computation if showing in live mode."""
292 if gray != self.__invalid_score:
293 self.__invalid_score = gray
294 color = gdk.color_parse(gray and "#999999" or "#000000")
295 self.txtScore.modify_text(gtk.STATE_NORMAL, color)
296 if gray and self.chkLive.get_active():
297 self.__queue_robot_live()
304 try:
305 self.chkPeq.set_sensitive(sens)
306 except:
307 pass
317 if data_prep and data_prep.workspaces:
318 return reduce(operator.add, (ws.data.Ndata for ws in data_prep.workspaces), 0)
319 else:
320 return self.__Ndata
331 - def add_trial_fields(self, row, mtree, prefix='', model_prep=None, data_prep=None):
332 add_trial_rates(row, mtree, prefix)
333 if (not prefix) or self.robot.optNchannel:
334 add_trial_nChannel(row, mtree, prefix)
335 if (not prefix) or self.robot.optCond or self.robot.optVar:
336 add_trial_amps(row, mtree, prefix)
353
354 - def calculate_score(self, model_prep=None, data_prep=None, wait=True, receiver=lambda score: None, on_trial=None, **kw):
357 - def optimize(self, model_prep=None, data_prep=None, wait=True, receiver=None, ext_return=False, on_trial=None):
362 - def __onOptDone(self, LL, iterations, grads, Hessian, workspaces, model_prep, data_prep):
363 self.QubX.Models.view.optimizing = False
364 self.LL = LL
365 self.iterations = iterations
366 self.grads = grads
367 iteration_messages = self.__nb_iter_buf.getvalue() if self.__nb_iter_buf else ""
368 self.__nb_iter_buf = cStringIO.StringIO()
369 for source in model_prep.sources:
370 source.undoStack.seal_undo('set rates')
371 Ndata = self.__Ndata
372 self.append_output('===================================================\n')
373 self.outStatus.write("""MacRates: finished optimizing %d points after %d iterations.
374 Data: %s
375 Model: %s
376 P0: %s
377 Resample: %s
378 Gleak: %.4g
379 Vleak: %.4g
380 GPU: %s
381 Optimize: %s
382 Gradient: %s
383 """ % (Ndata,
384 iterations,
385 '\n '.join(os.path.split(ws.data.file.path)[1] for ws in workspaces),
386 model_prep.sources and ', '.join([os.path.split(source.path)[1] for source in model_prep.sources]) or "from script",
387 self.robot.Peq and "equilibrium" or "fixed (States table)",
388 self.data.resample and ("by delta = %.4g" % self.data.resample_std) or "no",
389 self.robot.Gleak,
390 self.robot.Vleak,
391 self.chkGPU.get_active() and "yes" or "no",
392 ((self.robot.optNchannel and "channel count, " or "") +
393 (self.robot.optCond and "conductance, " or "") +
394 (self.robot.optVar and "variance, " or "") +
395 "rates"),
396 ', '.join('%8.5g' % x for x in grads)))
397 if workspaces:
398 self.__set_model_confidence(workspaces[0], model_prep)
399 self.outStatus.write('\nLL: ')
400 self.outStatus.write_bold('%.5g' % LL)
401 if self.last_LL:
402 self.outStatus.write(' last LL: %.5g' % self.last_LL)
403 self.append_output('\n===================================================\n')
404 self.invalidate_controls(False)
405 qubx.notebook.Notebook.send(qubx.notebook.NbText(iteration_messages), auto_id='MacRates_opt.Iterations')
406 qubx.notebook.Notebook.send(qubx.notebook.NbText(self.__nb_iter_buf.getvalue()), auto_id='MacRates_opt.Messages')
407 self.__nb_iter_buf = None
408 if self.QubX.Models.file in model_prep.sources:
409 qubx.notebook.Notebook.send(self.QubX.Models.view.nbPicture, auto_id='MacRates_opt.Model.Picture')
410 qubx.notebook.Notebook.send(self.QubX.Models.view.nbPictureNO, auto_id='MacRates_opt.Model.PictureNO')
411 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Classes').nbTable, auto_id='MacRates_opt.Model.Classes')
412 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Rates').nbTable, auto_id='MacRates_opt.Model.Rates')
413 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Kinetic Constraints').nbTable, auto_id='MacRates_opt.Model.Constraints')
414
415 self.fitbot.do(self.fitbot_send_datapic, 'MacRates_opt.Data.Picture')
416 self.last_opt_model = workspaces[0].param.modelinfo[0].fastmodel if workspaces else None
417 self.last_LL = LL
418 self.OnOptimized(iterations, LL, grads, Hessian)
420 for mi in ws.param.modelinfo:
421 self.outStatus.write("\n", scroll=False)
422 if self.robot.optNchannel:
423 self.outStatus.write("Channel count: ", scroll=False)
424 self.outStatus.write_bold('%i' % mi.fastmodel.Nchannel, scroll=False)
425 self.outStatus.write(" +/- %.1f\n" % mi.fastmodel.Nchannel_err, scroll=False)
426 if self.robot.optCond or self.robot.optVar:
427 amp_lbl, std_lbl = ('Amp', 'Variance') if (not mi.fastmodel.IeqFv) else ('Cond', 'CondVar')
428 if mi.fastmodel.IeqFv and mi.fastmodel.iVoltage:
429 if self.robot.optCond:
430 self.outStatus.write('Baseline Amp: ', scroll=False)
431 self.outStatus.write_bold('%.5g' % mi.fastmodel.amp0, scroll=False)
432 self.outStatus.write(' +/- %.5g\n' % mi.fastmodel.amp0_err, scroll=False)
433 if self.robot.optVar:
434 self.outStatus.write('Baseline Variance: ', scroll=False)
435 self.outStatus.write_bold('%.5g' % mi.fastmodel.var0, scroll=False)
436 self.outStatus.write(' +/- %.5g\n' % mi.fastmodel.var0_err, scroll=False)
437 self.outStatus.write('%7s' % 'State', scroll=False)
438 if self.robot.optCond:
439 self.outStatus.write_bold('%14s' % amp_lbl, scroll=False)
440 self.outStatus.write('%14s' % (amp_lbl+' err'), scroll=False)
441 if self.robot.optVar:
442 self.outStatus.write_bold('%14s' % std_lbl, scroll=False)
443 self.outStatus.write('%14s' % (std_lbl+' err'), scroll=False)
444 self.outStatus.write('\n', scroll=False)
445 for c in xrange(mi.fastmodel.Nclass):
446 self.outStatus.write('%7d' % c)
447 if self.robot.optCond:
448 self.outStatus.write_bold('%14.5g' % mi.fastmodel.amp[c], scroll=False)
449 self.outStatus.write('%14.5g' % mi.fastmodel.amp_err[c], scroll=False)
450 if self.robot.optVar:
451 self.outStatus.write_bold('%14.5g' % mi.fastmodel.var[c], scroll=False)
452 self.outStatus.write('%14.5g' % mi.fastmodel.var[c], scroll=False)
453 self.outStatus.write('\n', scroll=False)
454 self.outStatus.write('%10s%7s' % ("Rate From", "To"), scroll=False)
455 self.outStatus.write_bold('%14s' % "k0", scroll=False)
456 self.outStatus.write('%11s' % "+/-", scroll=False)
457 self.outStatus.write_bold('%14s' % 'k1', scroll=False)
458 self.outStatus.write('%11s' % "+/-", scroll=False)
459 self.outStatus.write_bold('%14s' % 'k2', scroll=False)
460 self.outStatus.write('%11s' % "+/-", scroll=False)
461 for mi, source in itertools.izip(ws.param.modelinfo, model_prep.sources):
462 self.outStatus.write('\n', scroll=False)
463 for rate in source.rates:
464 a,b = rate.From, rate.To
465 source.rates[rate.Index, 'k0 std'] = mi.fastmodel.K0_err[a,b]
466 source.rates[rate.Index, 'k1 std'] = mi.fastmodel.K1_err[a,b]
467 source.rates[rate.Index, 'k2 std'] = mi.fastmodel.K2_err[a,b]
468 self.outStatus.write('%10d%7d' % (a, b), scroll=False)
469 self.outStatus.write_bold('%14.5g' % rate.k0, scroll=False)
470 self.outStatus.write('%11.3g' % mi.fastmodel.K0_err[a,b], scroll=False)
471 self.outStatus.write_bold('%14.5g' % rate.k1, scroll=False)
472 self.outStatus.write('%11.3g' % mi.fastmodel.K1_err[a,b], scroll=False)
473 self.outStatus.write_bold('%14.5g' % rate.k2, scroll=False)
474 self.outStatus.write('%11.3g' % mi.fastmodel.K2_err[a,b], scroll=False)
475 self.outStatus.write('\n')
476 - def __onIteration(self, iteration, LL, grads, optNchannel, workspaces, modelinfo, K0s, K1s, K2s, amps, stds, amp0s, std0s, Nchannels):
477 self.LL = LL
478 self.iterations = iteration
479 self.grads = grads
480 for mi, K0, K1, K2, amp, std, amp0, std0, Nchannel in itertools.izip(modelinfo, K0s, K1s, K2s, amps, stds, amp0s, std0s, Nchannels):
481 model = mi.prep.source
482 if not model:
483 continue
484 K012ToModel(K0, K1, K2, model)
485 if model.IeqFv:
486 for c,a in enumerate(amp):
487 model.classes.set(c, 'Cond', a)
488 model.classes.set(c, 'CondStd', std[c])
489 model.classes.set(0, 'Amp', amp0)
490 model.classes.set(0, 'Std', std0)
491 else:
492 for c,a in enumerate(amp):
493 model.classes.set(c, 'Amp', a)
494 model.classes.set(c, 'Std', std[c])
495 model.set_channelCount(Nchannel, seal=False)
496 if (self.__Ndata > 1) and optNchannel and (len(modelinfo) == 1):
497 for i, ws in enumerate(workspaces):
498 datafile = qubx.global_namespace.QubX.Data.views[ws.param.data.file_index].file
499 try:
500 cc_ix = datafile.constants.index('Channel Count')
501 datafile.constants[cc_ix, 'Value'] = ws.param.modelinfo[0].fastmodel.Nchannel
502 except KeyError:
503 datafile.constants.append({'Name' : 'Channel Count', 'Value' : ws.param.modelinfo[0].fastmodel.Nchannel})
504 self.invalidate_controls(False)
505 self.outStatus.write('Iter: %i\tLL: %8.5g\tgrads: %s\n' % (iteration, LL, ', '.join("%8.5g"%g for g in grads)))
506 self.OnIteration(iteration, LL, grads, K0, K1, amp, std, amp0, std0, Nchannel)
507 self.OnIteration2(iteration, LL, grads, K0, K1, K2, amp, std, amp0, std0, Nchannel)
509 cancel()
510 for ws in self.robot.workspaces:
511 for mi in ws.param.modelinfo:
512 mi.fastmodel.obj[0].stop_flag = 1
534 - def request_data_prep(self, receiver, segs, model_prep, last_data_prep=None, resample=None, resample_std=None):
535 QubX = qubx.global_namespace.QubX
536 view_of_file = dict([(view.file, view) for view in QubX.Data.views])
537 view_segm = []
538 vs_by_file = {}
539 for seg in segs:
540 if not (seg.file in vs_by_file):
541 vs = (view_of_file[seg.file], [])
542 view_segm.append(vs)
543 vs_by_file[seg.file] = vs
544 vs_by_file[seg.file][1].append(seg)
545 if not view_segm:
546 if receiver:
547 gobject.idle_add(receiver, DataPrep(segs, []))
548 return
549
550 resamp = QubX.Modeling.MacRates.resample if (resample is None) else resample
551 resamp_std = QubX.Modeling.MacRates.resample_delta if (resample_std is None) else resample_std
552
553 datas = []
554 data_prep = DataPrep(segs, datas)
555 self.last_LL = 0.0
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572 rcv_stims = None
573 def rcv_hack(*args):
574 rcv_stims.send(args)
575 def receive_stims():
576 stim_names = iunion((prep.source or prep.model).variables for prep in model_prep.preps)
577 for dataview, segsrc in view_segm:
578
579 if last_data_prep and len(last_data_prep.datas) and (len(view_segm) == 1):
580 data = last_data_prep.datas[0]
581 data.reset(len(stim_names))
582 else:
583 data = max_mac_ll.Data(len(stim_names), file_index=QubX.Data.views.index(dataview))
584
585 sampling_sec = dataview.file.sampling
586 if 'Voltage' in stim_names:
587 data.iVoltage = stim_names.index('Voltage') + 1
588 else:
589 data.iVoltage = 0
590 data.signal_names = ['Current'] + stim_names
591 data.ff_ll_counts = []
592
593 iseg = -1
594 for seg in segsrc:
595 data.file = seg.file
596 data.iSignal = seg.signal
597 QubX.Modeling.Stimulus.call_with_stimulus_idl(rcv_hack, [(chunk.f, chunk.l) for chunk in seg.chunks], datafile=dataview.file, names=stim_names)
598 stim_names, stimidl = (yield)
599 chunkidl = [ Anon(dwellCounts=[], classeses=[], durationses=[], ampses=[]) for chunk in seg.chunks]
600 missing = False
601 for segs_flcda in stimidl:
602 for ichunk, flcda in enumerate(segs_flcda):
603 ff, ll, cc, dd, aa = flcda
604 if not len(cc):
605 missing = True
606 chunkidl[ichunk].dwellCounts.append(len(cc))
607 chunkidl[ichunk].classeses.append(cc)
608 chunkidl[ichunk].durationses.append(dd)
609 chunkidl[ichunk].ampses.append(aa)
610 if missing:
611 continue
612
613 iseg += 1
614 data.append_segment()
615 for ichunk, chunk in enumerate(seg.chunks):
616 if chunk.included:
617 Nd = chunk.n
618 chunkoff = chunk.f - seg.f
619 xx = arange(chunkoff, chunkoff+Nd, dtype='float32')
620 xx *= sampling_sec
621 yy = zeros(shape=(Nd,), dtype='float32')
622 ii = 0
623 for ch in generate_chunk_samples([chunk]):
624 yy[ii:ii+ch.n] = ch.samples
625 ii += ch.n
626 if resamp:
627 means, stds, ff, ll, closest = qubx.fast.data.adaptive_resample(yy, resamp_std)
628 xx = array(xx[closest], dtype='float32', copy=True)
629 yy = array(yy[closest], dtype='float32', copy=True)
630 ff += chunk.f
631 ll += chunk.f
632 Nd = len(means)
633 else:
634 ff = arange(seg.f+chunkoff, seg.f+chunkoff+Nd, dtype='int32')
635 ll = ff
636 data.ff_ll_counts.append( (ff, ll, Nd) )
637 else:
638 Nd = 0
639 xx = yy = None
640 data.append_chunk(iseg, (chunk.l - chunk.f + 1) * sampling_sec, Nd, xx, yy, None,
641 chunkidl[ichunk].dwellCounts, chunkidl[ichunk].classeses,
642 chunkidl[ichunk].durationses, chunkidl[ichunk].ampses)
643 datas.append(data)
644 gobject.idle_add(receiver, data_prep)
645 yield None
646
647 rcv_stims = receive_stims()
648 rcv_stims.next()
650 self.chkGPU.set_active(False)
651 self.chkGPU.set_sensitive(False)
652 self.chkGPU.set_tooltip_text('no OpenCL-compatible graphics processor is available')
654 """Waits until all pending work is done on the thread which updates fit curves, then returns."""
655 while self.fitbot.last_serial != self.fitbot.serial:
656 qubx.pyenv.env.process_events()
675 model_prep = self.robot.model_prep
676 self.robot.models = model_prep.models
677 self.robot.modelinfo = [Anon(model=prep.model, prep=prep) for prep in model_prep.preps if prep.model.states.size]
678 self.data.model_prep = model_prep
679
680 for mi in self.robot.modelinfo:
681 mi.clazz = ModelToClazz(mi.model)
682 mi.Nclass = int(max(mi.clazz) + 1) if mi.clazz.shape[0] else 0
683
684 datas = self.robot.data = self.data_prep.datas
685 if self.robot.workspaces:
686 for ws in self.robot.workspaces:
687 try:
688 ws.fastmodel.dispose()
689 except:
690 pass
691 workspaces = self.robot.workspaces = []
692 self.__Ndata = reduce(operator.add, (data.Ndata for data in datas), 0)
693
694 for data in datas:
695 stimclasses = data.get_stimclasses()
696 Nstim = data.Nstim
697 Nsig = data.Nsig
698 sc_frac = data.get_stimclass_frac()
699 param = max_mac_ll.Param(len(self.robot.modelinfo))
700 param.modelinfo = []
701 param.data = data
702 param.useVar = True
703 param.optNchannel = self.robot.optNchannel
704 param.accel = self.robot.accel
705
706 for im, mi in enumerate(self.robot.modelinfo):
707 fastmodel = ModelToFast(mi.model, data.signal_names, self.robot_on_status)
708 fastmodel.usePeq = self.robot.Peq
709 fastmodel.iVoltage = data.iVoltage
710 fastmodel.Gleak = self.robot.Gleak
711 fastmodel.Vleak = self.robot.Vleak
712 K0, K1, K2, L, V, P = ModelToRateMatricesP(mi.model, data.signal_names)
713 fastmodel.K0[:], fastmodel.K1[:], fastmodel.K2[:] = K0, K1, K2 = self.robot.K012[im]
714
715 if len(datas) > 1:
716 try:
717 fastmodel.Nchannel = int(round(qubx.global_namespace.QubX.Data.views[data.file_index].file.constants.get_by_name('Channel Count', 'Value')))
718 except:
719 traceback.print_exc()
720 pass
721
722 ratesm = qubx.fast.model.StimRates(fastmodel, Nstim, Nsig, stimclasses, sc_frac, 0.0, custom_eigen=None)
723 A, B = mi.model.constraints_kin.get_matrices_p(K0, K1, K2, L, V, P, auto=False)
724 if A != None:
725 ratesm.cns.Ain[:A.shape[0],:A.shape[1]] = A
726 ratesm.cns.Bin[:B.shape[0]] = B.flatten()
727 ratesm.cns.Ncns = B.shape[0]
728 else:
729 ratesm.cns.Ncns = 0
730
731 ampm = qubx.fast.model.StimAmps(fastmodel, Nstim, Nsig, stimclasses, sc_frac)
732 if not self.robot.optCond:
733 for c in xrange(fastmodel.Nclass):
734 ampm.fix_amp(c)
735 if fastmodel.IeqFv:
736 ampm.fix_amp0()
737 if not self.robot.optVar:
738 for c in xrange(fastmodel.Nclass):
739 ampm.fix_var(c)
740 if fastmodel.IeqFv:
741 ampm.fix_var0()
742 param.add_model(fastmodel, ampm, ratesm)
743 param.modelinfo.append(mi)
744 mi.fastmodel = fastmodel
745 mi.Nchannel = fastmodel.Nchannel
746
747 if param.models:
748 param.setup_constraints()
749 workspaces.append(Anon(param=param, data=data, modelinfo=param.modelinfo, L=L, V=V))
751 if self.__invalid_live and not (self.robot.working or self.optimizing):
752 if model_prep is None:
753 with self.robot.main_hold:
754 mp = self.model_prep.val
755 else:
756 mp = model_prep
757 self.robot_setup_data(mp, None)
758 LL = self.robot_calculate_score()
759 gobject.idle_add(self.OnLiveLL, LL)
761 LL = 0.0
762 try:
763 self.robot.working = True
764 self.__invalid_live = False
765 if not self.optimizing:
766 self.robot_setup()
767 LL = 0.0
768 if ('RejectMacModels' in qubx.global_namespace.__dict__) and qubx.global_namespace.RejectMacModels([
769 mi.fastmodel for mi in self.robot.modelinfo
770 ]):
771 LL = -1e20
772 else:
773 for ws in self.robot.workspaces:
774 try:
775 LL += max_mac_ll.mac_ll(ws.param)
776 except:
777 gobject.idle_add(self.outStatus.write, traceback.format_exc() + '\n')
778 LL += -1.0
779 if ws.param.gpu_fail:
780 gobject.idle_add(self.__onGPUFail)
781 self.robot.accel -= 1
782 for ws2 in self.robot.workspaces:
783 ws2.param.accel = self.robot.accel
784 self.robot_queue_fits(0, True)
785 except:
786 self.robot.send_exception()
787 gobject.idle_add(self.__onGotLL, LL)
788 self.robot.working = False
789 return LL
791 """Returns the list of initial parameter values, on worker thread; override this method.
792 Derive parameter values from self.robot.model and/or from your own widgets."""
793 self.robot_setup()
794 if self.robot.workspaces:
795 pars = [float(x) for x in self.robot.workspaces[0].param.fPars[:self.robot.workspaces[0].param.NfPar]]
796 else:
797 pars = []
798
799 if (len(self.robot.workspaces) > 1) and self.robot.optNchannel and (len(self.robot.models) == 1):
800 pars.extend([1.0] * (len(self.robot.workspaces)-1))
801 self.__scale_Nchannel = [log(ws.param.Nchannel) for ws in self.robot.workspaces[1:]]
802 return array(pars)
804 if (pars != None) and self.robot.workspaces:
805 NfPar = self.robot.workspaces[0].param.NfPar
806 self.robot.workspaces[0].param.fPars[:NfPar] = pars[:NfPar]
807 self.robot.workspaces[0].param.do_fPars_to_model()
808 if (len(self.robot.workspaces) > 1) and self.robot.optNchannel and (len(self.robot.models) == 1):
809 ccpar = pars[-(len(self.robot.workspaces)-1):]
810 for i, ws in enumerate(self.robot.workspaces[1:]):
811 float_Nchannel = max(1.0, exp(self.__scale_Nchannel[i] * ccpar[i]))
812 ws.param.float_Nchannel = float_Nchannel
813 ws.param.modelinfo[0].fastmodel.Nchannel = int(round(float_Nchannel))
815 try:
816 self.robot.working = True
817 self.robot.LL = 0.0
818 self.__last_fits_iter = -1
819 iterations = 0
820 pars = grads = []
821 Hessian = None
822 self.robot.LL, pars, iterations, grads, Hessian = super(RatesFace, self).robot_optimize()
823 self.robot.workspaces[0].param.calc_std(Hessian)
824 if self.accel and (self.__last_fits_iter < iterations):
825 self.robot_queue_fits(iterations, True)
826 except:
827 self.robot.send_exception()
828 gobject.idle_add(self.__onOptDone, self.robot.LL, iterations, grads, Hessian, self.robot.workspaces, self.robot.model_prep, self.data_prep)
829 self.robot.working = False
830 return self.robot.LL, pars, iterations, grads, Hessian
832 if self.robot.workspaces[0].param.modelinfo[0].fastmodel.obj[0].stop_flag:
833 raise KeyboardInterrupt()
834 else:
835 self.robot_set_pars(pars)
836 self.robot_queue_fits(iterations)
837 self.robot.LL = score
838 gobject.idle_add(self.__onIteration, iterations, - score, grads, self.robot.optNchannel,
839 self.robot.workspaces, self.robot.workspaces[0].param.modelinfo,
840 [array(mi.fastmodel.K0, copy=True) for mi in self.robot.workspaces[0].param.modelinfo],
841 [array(mi.fastmodel.K1, copy=True) for mi in self.robot.workspaces[0].param.modelinfo],
842 [array(mi.fastmodel.K2, copy=True) for mi in self.robot.workspaces[0].param.modelinfo],
843 [array(mi.fastmodel.amp, copy=True) for mi in self.robot.workspaces[0].param.modelinfo],
844 [numpy.sqrt(array(mi.fastmodel.var, copy=True)) for mi in self.robot.workspaces[0].param.modelinfo],
845 [mi.fastmodel.amp0 for mi in self.robot.workspaces[0].param.modelinfo],
846 [sqrt(mi.fastmodel.var0) for mi in self.robot.workspaces[0].param.modelinfo],
847 [mi.fastmodel.Nchannel for mi in self.robot.workspaces[0].param.modelinfo])
849 if (not force) and self.optimizing and self.accel:
850 if (datetime.datetime.now() - self.__last_fits_time).seconds < MIN_SECONDS_BETWEEN_FIT:
851 return
852 self.__last_fits_time = datetime.datetime.now()
853 self.__last_fits_iter = iteration
854 self.fitbot.serial += 1
855 for data in self.robot.data:
856
857 chunks = []
858 samples = data.get_buffer()
859 Ndata = data.Ndata
860 start = 0
861 for firsts, lasts, count in data.ff_ll_counts:
862 means = numpy.array(samples[start:start+count, 0], copy=True)
863 stds = numpy.sqrt(array(samples[start:start+count, 1]))
864
865
866 chunks.append((firsts, lasts, means, stds))
867 start += count
868 means[numpy.isinf(means)] = 0.0
869 means[numpy.isnan(means)] = 0.0
870 stds[numpy.isinf(stds)] = 0.0
871 stds[numpy.isnan(stds)] = 0.0
872 self.fitbot.do(self.fitbot_set_fits, data.file, data.iSignal, chunks, self.fitbot.serial)
873
875 gobject.idle_add(self.outStatus.write, s+"\n")
877
878 if serial == self.fitbot.serial:
879 gobject.idle_add(self.do_set_fits, datafile, iSignal, chunks)
880 self.fitbot.last_serial = serial
882 fits = datafile.fits[iSignal].idl
883 fits.clear()
884 for ff, ll, mm, ss in chunks:
885 fits.set_fit(ff, ll, mm, ss)
886 datafile.OnChangeFits(datafile, iSignal)
887
890
891 def set_rates2(*K012s):
892 for prep, K012 in itertools.izip(mp.preps, K012s):
893 prep.K0, prep.K1, prep.K2 = K012
894 K012ToModel(prep.K0, prep.K1, prep.K2, prep.model)
895 return set_rates2
896
910 if self.__group == x:
911 return
912 self.__group = x
913 self.OnChange()
914 group = property(lambda self: self.__group, lambda self, x: self.set_group(x))
916 events = (model.OnSetChannelCount,
917 model.OnSetIeqFv,
918 model.OnSetVRev,
919 model.states.OnSet,
920 model.states.OnInsert,
921 model.states.OnRemoved,
922 model.classes.OnSet,
923 model.rates.OnInsert,
924 model.rates.OnRemoved,
925 model.OnChangeBalanceLoops,
926 model.constraints_kin.OnEdit)
927 if adding:
928 for event in events:
929 event += self.__ref(self.__onChangeModel)
930 model.rates.OnSet += self.__ref(self.__onSetRate)
931 else:
932 for event in events:
933 event -= self.__ref(self.__onChangeModel)
934 model.rates.OnSet -= self.__ref(self.__onSetRate)
935 self.OnChange()
940 - def __onSetTable(self, index, field_name, val, prev, undoing):
941 if field_name == 'Group':
942 self.OnChange()
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971