Package qubx_macro_r :: Module rates
[hide private]
[frames] | no frames]

Source Code for Module qubx_macro_r.rates

  1  """Provides a visual panel (L{qubx.faces.Face}) to optimize rate constants from ensemble measurement. 
  2   
  3  /* Copyright 2008-2013 Research Foundation State University of New York   */ 
  4   
  5  /* This file is part of QUB Express.                                      */ 
  6   
  7  /* QUB Express is free software; you can redistribute it and/or modify    */ 
  8  /* it under the terms of the GNU General Public License as published by   */ 
  9  /* the Free Software Foundation, either version 3 of the License, or      */ 
 10  /* (at your option) any later version.                                    */ 
 11   
 12  /* QUB Express is distributed in the hope that it will be useful,         */ 
 13  /* but WITHOUT ANY WARRANTY; without even the implied warranty of         */ 
 14  /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */ 
 15  /* GNU General Public License for more details.                           */ 
 16   
 17  /* You should have received a copy of the GNU General Public License,     */ 
 18  /* named LICENSE.txt, in the QUB Express program directory.  If not, see  */ 
 19  /* <http://www.gnu.org/licenses/>.                                        */ 
 20   
 21  """ 
 22   
 23  import cStringIO 
 24  import itertools 
 25  import gc 
 26  import operator 
 27  import os 
 28  import sys 
 29  import time 
 30  import traceback 
 31   
 32  from qubx.data_types import * 
 33  from qubx.util_types import * 
 34  from qubx.faces import * 
 35  from qubx.GTK import * 
 36  import qubx.fast.data 
 37  import qubx.fast.model 
 38  import qubx.notebook 
 39  import qubx.optimize 
 40  import qubx.optimizeGTK 
 41  import qubx.pyenv 
 42  import qubx.tree 
 43  from qubx.model import * 
 44  from qubx.task import * 
 45  from qubx.settings import Propertied, Property 
 46  from qubx.trialset import add_trial_rates, add_trial_amps, add_trial_nChannel 
 47  import macro_r_lib 
 48  from . import qubx_plugin 
 49   
 50  import scipy.optimize 
 51  from numpy import * 
 52   
 53  import ctypes 
 54  from ctypes import c_int, c_float, c_double, c_void_p, c_char_p, Structure, POINTER, CFUNCTYPE, byref, sizeof, py_object, pointer, cast 
 55  pybuf = ctypes.pythonapi.PyBuffer_FromReadWriteMemory 
 56  pybuf.restype = py_object 
 57   
 58   
 59  MIN_SECONDS_BETWEEN_FIT = 3 
 60  MAXITER = 300 
61 62 -class DataPrep(Anon):
63 """Represents data ready for computation."""
64 - def __init__(self, segs, datas):
65 Anon.__init__(self, segs=segs, datas=datas)
66 67 68 69 #class MacData(Requestable): 70 # """Tracks data source, prepares data and stimulus as needed for LL, opt. Data is 71 #[L{macro_r_lib.Data}], each item decorated with additional fields: 72 # - iVoltage: index of voltage signal 73 # - signal_names: list of strings 74 # - file: L{qubx.data_types.QubData} 75 # - ff_ll_counts: [(firsts, lasts, count)] -- resampled interval bounds, per data chunk 76 # 77 # @ivar resample: True if data should be resampled by delta next time 78 # @ivar resample_std: the "delta" of resample-by-delta 79 # @ivar use_group: a Group index in the Data table, to fit multi files, or None to use the Data Source 80 #""" 81 82 @Propertied(Property('use_Peq', True, "True to start each segment from equilibrium; False to use States:Pr"), 83 Property('use_group', False, "True to fit a group of data files", in_tree=False), 84 Property('use_group_index', 1, "Group of interest in Data table"), 85 Property('resample', False, "True to resample by delta y"), 86 Property('resample_delta', 0.5, 'an interval with std < delta is resampled as one data point'), 87 Property('Gleak', 0.0, 'Leakage conductance (pS)'), 88 Property('Vleak', 0.0, 'Leak voltage (mV)'), 89 Property('opt_Nchannel', False, "True to optimize the model's channel count"), 90 Property('opt_cond', False, "True to optimize the model's Amps/Conds"), 91 Property('opt_var', False, "True to optimize the model's Stds/CondStds"), 92 Property('recursive_fit', False, "Toggle non-recursive (MacRates) and recursive (MacroR) fits"), 93 # not persistent: 94 Property('live', False, "True to redo fit curve when model changes", in_tree=False), 95 Property('use_GPU', False, "True to use OpenCL-capable graphics card, if avail", in_tree=False) 96 )
97 -class RatesFace(qubx_plugin.OptToolPlugin):
98 """Calculates log-likelihood and optimizes model parameters, a la Moffatt 2007. 99 100 @ivar OnIterationMR: L{WeakEvent}(iteration, LL, grads, K0, K1, amp, std, amp0, std0, Nchannel) 101 @ivar OnIteration2MR: L{WeakEvent}(iteration, LL, grads, K0, K1, K2, amp, std, amp0, std0, Nchannel) 102 @ivar OnLiveLL: L{WeakEvent}(LL) 103 """ 104 __explore_featured = ['accel', 'rates', 'OnIterationMR', 'LL', 'last_LL', 'grads', 'OnLiveLL', 'fitbot', 'outStatus', 105 'get_ndata', 'add_trial_fields', 'wait_for_fits']
106 - def __init__(self, name='MacroR', global_name='QubX.Modeling.MacroR'):
107 super(RatesFace, self).__init__(name, global_name, 108 "qubx_macror_optimize", 109 {"max step" : 0.1, 110 "num grad dx" : 1e-4, 111 "conv ll" : 1e-3, 112 "conv grad" : 1e-3}) 113 self.__ref = Reffer() 114 self.__invalid_score = False 115 self.__invalid_live = True 116 self.accel = 1 # (0: single; 1: openmp; 2: opencl) 117 self.QubX = qubx.pyenv.env.globals['QubX'] 118 self.QubX.OnQuit += self.__ref(self.mac_finalize) 119 self.propertied_connect_settings('MacroR') 120 self.data.use_group = self.use_group_index if self.use_group else None 121 self.rates = Product(self.__get_rates) # overriding OptToolPlugin.rates 122 self.rates.OnInvalidate += self.__ref(self.__onInvalidateRates) 123 self.OnIterationMR = WeakEvent() 124 self.OnIteration2MR = WeakEvent() 125 126 self.robot.working = False 127 self.robot.workspaces = None 128 self.robot.optNchannel = self.robot.optCond = self.robot.optVar = False 129 self.iterations = 0 130 self.LL = self.last_LL = 0.0 131 self.grads = [] 132 self.__Ndata = 0 133 self.OnLiveLL = WeakEvent() 134 135 # When calculation is fast enough, showing fit curves can be the slowest part of an iteration. 136 # Ideally we'd skip displaying an iteration's curves if the optimizer has already finished the next iteration. 137 # Showing curves on a separate thread makes it easier: 138 self.fitbot = Robot('Apply Fit') # not in task list 139 self.fitbot.OnException += self.__ref(self.on_exception) 140 self.fitbot.serial = self.fitbot.last_serial = 0 141 # We also skip iterations when too few seconds have elapsed: 142 self.__last_fits_time = datetime.datetime.now() 143 # rewind the clock so fit curves can show right away (there must be a simpler way): 144 if self.__last_fits_time.hour: 145 self.__last_fits_time = self.__last_fits_time.replace(hour=self.__last_fits_time.hour-1) 146 # else: it will not show any fits for the first few seconds oh well 147 148 # these figures will be published through Admin:Notebook 149 qubx.notebook.Notebook.register_auto('MacroR_eval.Messages', 'Messages, on MacroR: Get LL', False) 150 qubx.notebook.Notebook.register_auto('MacroR_eval.Model.Picture', 'Model picture, on MacroR: Get LL', False) 151 qubx.notebook.Notebook.register_auto('MacroR_eval.Model.PictureNO', 'Model picture, no overlays, on MacroR: Get LL', False) 152 qubx.notebook.Notebook.register_auto('MacroR_eval.Data.Picture', 'Data picture, on MacroR: Get LL', False) 153 qubx.notebook.Notebook.register_auto('MacroR_eval.Model.Classes', 'Classes table, on MacroR: Get LL', False) 154 qubx.notebook.Notebook.register_auto('MacroR_eval.Model.Rates', 'Rates table, on MacroR: Get LL', False) 155 156 qubx.notebook.Notebook.register_auto('MacroR_opt.Iterations', 'Iteration log, on MacroR: Optimize', True) 157 qubx.notebook.Notebook.register_auto('MacroR_opt.Messages', 'Messages, on MacroR: Optimize', True) 158 qubx.notebook.Notebook.register_auto('MacroR_opt.Model.Picture', 'Model picture, on MacroR: Optimize', True) 159 qubx.notebook.Notebook.register_auto('MacroR_opt.Model.PictureNO', 'Model picture, no overlays, on MacroR: Optimize', False) 160 qubx.notebook.Notebook.register_auto('MacroR_opt.Data.Picture', 'Data picture, on MacroR: Optimize', True) 161 qubx.notebook.Notebook.register_auto('MacroR_opt.Model.Classes', 'Classes table, on MacroR: Optimize', True) 162 qubx.notebook.Notebook.register_auto('MacroR_opt.Model.Rates', 'Rates table, on MacroR: Optimize', False) 163 qubx.notebook.Notebook.register_auto('MacroR_opt.Model.Constraints', 'Kinetic Constraints table, on MacroR: Optimize', True) 164 165 col = pack_item(gtk.VBox(), self.top_panel) 166 self.chkPeq = pack_check('start at equilibrium p', col) 167 self.chkPeq.set_tooltip_text('otherwise use States:Pr') 168 self.propertied_connect_check('use_Peq', self.chkPeq) 169 h = pack_item(gtk.HBox(), col) 170 self.chkResample = pack_check('resample by delta:', h) 171 self.chkResample.set_tooltip_text('skip slow changes:\ncompress data with std.dev < delta into a single point') 172 self.propertied_connect_check('resample', self.chkResample) 173 self.txtResample = pack_item(NumEntry(self.resample_delta, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h) 174 self.propertied_connect_NumEntry('resample_delta', self.txtResample) 175 176 pack_space(self.top_panel, x=20) 177 col = pack_item(gtk.VBox(), self.top_panel) 178 h = pack_item(gtk.HBox(), col) 179 pack_label('Gleak:', h) 180 self.txtGleak = pack_item(NumEntry(self.Gleak, acceptFloat, '%.3g', width_chars=6), h, at_end=True) 181 self.propertied_connect_NumEntry('Gleak', self.txtGleak) 182 h = pack_item(gtk.HBox(), col) 183 pack_label('Vleak:', h) 184 self.txtVleak = pack_item(NumEntry(self.Vleak, acceptFloat, '%.3g', width_chars=6), h, at_end=True) 185 self.propertied_connect_NumEntry('Vleak', self.txtVleak) 186 187 pack_space(self.top_panel, expand=True) 188 col = pack_item(gtk.VBox(True), self.top_panel) 189 lbl_d = pack_label('Multi-data:', col) 190 #lbl_m = pack_label('Multi-model:', col) 191 col = pack_item(gtk.VBox(True), self.top_panel) 192 h = pack_item(gtk.HBox(), col) 193 self.chkUseGroup = pack_check('Group', h) 194 self.propertied_connect_check('use_group', self.chkUseGroup) 195 self.txtUseGroup = pack_item(qubx.GTK.NumEntry(self.use_group_index, acceptIntGreaterThan(-1), width_chars=3), h) 196 self.txtUseGroup.set_sensitive(self.use_group) 197 self.propertied_connect_NumEntry('use_group_index', self.txtUseGroup) 198 for widget in (lbl_d, self.chkUseGroup, self.txtUseGroup): 199 widget.set_tooltip_text('To select data files, set their Group number in the Data table.\nThey will be fit to common parameters') 200 #h = pack_item(gtk.HBox(), col) 201 #self.chkModelsGroup = pack_check('Group', h) 202 #self.propertied_connect_check('multi_model', self.chkModelsGroup) 203 #self.txtModelsGroup = pack_item(qubx.GTK.NumEntry(self.multi_model_group, acceptIntGreaterThan(-1), width_chars=3), h) 204 #self.txtModelsGroup.set_sensitive(self.multi_model) 205 #self.propertied_connect_NumEntry('multi_model_group', self.txtModelsGroup) 206 #for widget in (lbl_m, self.chkModelsGroup, self.txtModelsGroup): 207 # 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.") 208 209 pack_space(self.top_panel, expand=True) 210 col = pack_item(gtk.VBox(True), self.top_panel) 211 self.chkLive = pack_check('live ', col) 212 self.chkLive.set_tooltip_text('update LL and fit curve when the model changes') 213 self.propertied_connect_check('live', self.chkLive) 214 self.chkRecursiveFit = pack_check('rec. fit', col) 215 self.chkRecursiveFit.set_tooltip_text('on: shows the recursive prior distribution;\noff: shows the MacRates (non-recursive) fit') 216 self.propertied_connect_check('recursive_fit', self.chkRecursiveFit) 217 self.chkGPU = pack_check('use GPU ', col, show=False) 218 self.chkGPU.set_tooltip_text('use graphics card if available; faster but less accurate') 219 self.propertied_connect_check('use_GPU', self.chkGPU) 220 221 self.lblScore.set_text('LL:') 222 self.btnGetScore.set_label('Get LL') 223 224 pack_label('Optimize:', self.right_panel) 225 self.chkOptNchannel = pack_check('channel count', self.right_panel) 226 self.propertied_connect_check('opt_Nchannel', self.chkOptNchannel) 227 self.chkOptCond = pack_check('conductance', self.right_panel) 228 self.propertied_connect_check('opt_cond', self.chkOptCond) 229 self.chkOptVar = pack_check('variance', self.right_panel) 230 self.propertied_connect_check('opt_var', self.chkOptVar) 231 self.chkOptRates = pack_check('rates', self.right_panel, active=True) 232 self.chkOptRates.set_tooltip_text('To hold a rate constant, right-click it, "Add Constraint...," "Fix..."') 233 self.chkOptRates.set_sensitive(False) 234 235 self.outStatus = TextViewAppender(self.txtStatus) 236 self.outStatus.write('Based on "Estimation of ion channel kinetics from fluctuations of macroscopic currents," Moffatt L., Biophys J. 2007 Jul 1; 93(1):74-91\n') 237 self.outStatus.OnWrite += self.__ref(self.__onWriteStatus) 238 self.__nb_iter_buf = None
239 - def mac_finalize(self):
240 """Stops the worker threads; call this in qubx_plugin_fini.""" 241 self.robot.stop() 242 self.fitbot.stop() 243 self.data = self.data_prep = None
244 - def on_exception(self, robot, typ, val, trace):
245 traceback.print_exception(typ, val, trace) 246 traceback.print_exception(typ, val, trace, file=self.outStatus)
247 - def __onWriteStatus(self, msg):
248 if not (self.__nb_iter_buf is None): 249 self.__nb_iter_buf.write(msg)
250 - def propertied_set(self, value, name):
251 if (name, value) == ('use_group_index', None): 252 self.use_group = False 253 return 254 super(RatesFace, self).propertied_set(value, name) 255 if name in ('use_Peq', 'Gleak', 'Vleak'): 256 self.invalidate_controls() 257 if name in ('resample', 'resample_delta'): 258 self.data.invalidate() 259 if name in ('use_group', 'use_group_index'): 260 self.data.use_group = self.use_group_index if self.use_group else None 261 self.txtUseGroup.set_sensitive(self.use_group) 262 if (name == 'use_group_index') and not self.use_group: 263 self.use_group = True 264 if name == 'live': 265 if value: 266 self.invalidate_controls() 267 if name == 'use_GPU': 268 self.invalidate_controls()
269 - def __onInvalidateRates(self, prod):
271 - def invalidate_controls(self, gray=True):
272 """Changes LL text color gray (outdated) or black (up-to-date), and starts computation if showing in live mode.""" 273 if self.__invalid_score != gray: 274 self.__invalid_score = gray 275 color = gdk.color_parse(gray and "#999999" or "#000000") 276 self.txtScore.modify_text(gtk.STATE_NORMAL, color) 277 if gray and self.showing and self.chkLive.get_active(): 278 self.__queue_robot_live()
279 - def __queue_robot_live(self):
280 if self.recursive_fit: 281 self.__invalid_live = True 282 if not (self.robot.working or self.optimizing): 283 self.__capture_robot_settings() 284 self.robot.do(self.robot_live) 285 elif hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates'): 286 MacRates = qubx.global_namespace.QubX.Modeling.MacRates 287 if not MacRates.live: 288 MacRates.live = True 289 MacRates.live = False
290 - def __sensitize(self, sens):
291 try: 292 self.chkPeq.set_sensitive(sens) 293 except: 294 pass
295 - def on_robot_begin(self):
296 super(RatesFace, self).on_robot_begin() 297 gobject.idle_add(self.__sensitize, False)
298 - def on_robot_end(self):
299 super(RatesFace, self).on_robot_end() 300 gobject.idle_add(self.__sensitize, True)
301 - def fitbot_send_datapic(self, auto_id):
302 gobject.idle_add(lambda: qubx.notebook.Notebook.send(self.QubX.Data.view.nbPicture, auto_id=auto_id))
303 - def get_ndata(self, data_prep=None):
304 if data_prep and data_prep.workspaces: 305 return reduce(operator.add, (ws.data.Ndata for ws in data_prep.workspaces), 0) 306 else: 307 return self.__Ndata
308 - def __get_rates(self):
309 model_prep = self.model_prep.val 310 signal_names = iunion((prep.source or prep.model).variables for prep in model_prep.preps) 311 return [ModelToRateMatricesP(prep.source or prep.model, signal_names)[:3] for prep in model_prep.preps]
312 - def add_trial_fields(self, row, mtree, prefix='', model_prep=None, data_prep=None):
313 add_trial_rates(row, mtree, prefix) 314 if (not prefix) or self.robot.optNchannel: 315 add_trial_nChannel(row, mtree, prefix) 316 if (not prefix) or self.robot.optCond or self.robot.optVar: 317 add_trial_amps(row, mtree, prefix)
318 - def __onGotLL(self, LL):
319 self.LL = LL 320 if not self.optimizing: 321 qubx.notebook.Notebook.send(qubx.notebook.NbText("""MacroR: Get LL: %.6g""" % LL), auto_id='MacroR_eval.Messages') 322 if self.QubX.Models.file in self.robot.model_prep.sources: ### TODO: send all sources 323 qubx.notebook.Notebook.send(self.QubX.Models.view.nbPicture, auto_id='MacroR_eval.Model.Picture') 324 qubx.notebook.Notebook.send(self.QubX.Models.view.nbPictureNO, auto_id='MacroR_eval.Model.PictureNO') 325 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Classes').nbTable, auto_id='MacroR_eval.Model.Classes') 326 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Rates').nbTable, auto_id='MacroR_eval.Model.Rates') 327 # fit robot send datapic with updated fit curves 328 fitbot = qubx.global_namespace.QubX.Modeling.MacRates.fitbot if ((not self.recursive_fit) and hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates')) else self.fitbot 329 fitbot.do(self.fitbot_send_datapic, 'MacroR_eval.Data.Picture') 330 if self.__invalid_live: 331 self.__queue_robot_live() 332 else: 333 self.invalidate_controls(False) 334 self.set_score(LL) 335 self.last_LL = LL
336 - def calculate_score(self, model_prep=None, data_prep=None, wait=True, receiver=lambda score: None, on_trial=None, **kw):
337 self.__capture_robot_settings() 338 return super(RatesFace, self).calculate_score(model_prep, data_prep, wait, receiver, on_trial, **kw)
339 - def optimize(self, model_prep=None, data_prep=None, wait=True, receiver=None, ext_return=False, on_trial=None):
340 self.__nb_iter_buf = cStringIO.StringIO() 341 self.__capture_robot_settings() 342 self.QubX.Models.view.optimizing = True 343 return super(RatesFace, self).optimize(model_prep, data_prep, wait, receiver, ext_return, on_trial)
344 - def __onOptDone(self, LL, iterations, grads, Hessian, workspaces, model_prep, data_prep):
345 self.QubX.Models.view.optimizing = False 346 self.LL = LL 347 self.iterations = iterations 348 self.grads = grads 349 iteration_messages = self.__nb_iter_buf.getvalue() if self.__nb_iter_buf else "" 350 self.__nb_iter_buf = cStringIO.StringIO() # to capture final output for notebook 351 for source in model_prep.sources: 352 source.undoStack.seal_undo('set rates') 353 Ndata = self.__Ndata 354 self.append_output('===================================================\n') 355 self.outStatus.write("""MacroR: finished optimizing %d points after %d iterations. 356 Data: %s 357 Model: %s 358 P0: %s 359 Resample: %s 360 Gleak: %.4g 361 Vleak: %.4g 362 GPU: %s 363 Optimize: %s 364 Gradient: %s 365 """ % (Ndata, 366 iterations, 367 '\n '.join(os.path.split(ws.data.file.path)[1] for ws in workspaces), 368 model_prep.sources and ', '.join([os.path.split(source.path)[1] for source in model_prep.sources]) or "from script", 369 self.robot.Peq and "equilibrium" or "fixed (States table)", 370 self.data.resample and ("by delta = %.4g" % self.data.resample_std) or "no", 371 self.robot.Gleak, 372 self.robot.Vleak, 373 self.chkGPU.get_active() and "yes" or "no", 374 ((self.robot.optNchannel and "channel count, " or "") + 375 (self.robot.optCond and "conductance, " or "") + 376 (self.robot.optVar and "variance, " or "") + 377 "rates"), 378 ', '.join('%8.5g' % x for x in grads))) 379 if workspaces: 380 self.__set_model_confidence(workspaces[0], model_prep) 381 self.outStatus.write('\nLL: ') 382 self.outStatus.write_bold('%.5g' % LL) 383 if self.last_LL: 384 self.outStatus.write(' last LL: %.5g' % self.last_LL) 385 self.append_output('\n===================================================\n') 386 self.invalidate_controls(False) 387 qubx.notebook.Notebook.send(qubx.notebook.NbText(iteration_messages), auto_id='MacroR_opt.Iterations') 388 qubx.notebook.Notebook.send(qubx.notebook.NbText(self.__nb_iter_buf.getvalue()), auto_id='MacroR_opt.Messages') 389 self.__nb_iter_buf = None 390 if self.QubX.Models.file in model_prep.sources: ### TODO: send all source models 391 qubx.notebook.Notebook.send(self.QubX.Models.view.nbPicture, auto_id='MacroR_opt.Model.Picture') 392 qubx.notebook.Notebook.send(self.QubX.Models.view.nbPictureNO, auto_id='MacroR_opt.Model.PictureNO') 393 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Classes').nbTable, auto_id='MacroR_opt.Model.Classes') 394 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Rates').nbTable, auto_id='MacroR_opt.Model.Rates') 395 qubx.notebook.Notebook.send(self.QubX.Tables.find_view('Kinetic Constraints').nbTable, auto_id='MacroR_opt.Model.Constraints') 396 # fit robot send datapic with updated fit curves 397 fitbot = qubx.global_namespace.QubX.Modeling.MacRates.fitbot if ((not self.recursive_fit) and hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates')) else self.fitbot 398 fitbot.do(self.fitbot_send_datapic, 'MacroR_opt.Data.Picture') 399 self.last_opt_model = workspaces[0].param.modelinfo[0].fastmodel if workspaces else None 400 self.last_LL = LL
401 - def __set_model_confidence(self, ws, model_prep):
402 for mi in ws.param.modelinfo: 403 self.outStatus.write("\n", scroll=False) # model name? 404 if self.robot.optNchannel: 405 self.outStatus.write("Channel count: ", scroll=False) 406 self.outStatus.write_bold('%i' % mi.fastmodel.Nchannel, scroll=False) 407 self.outStatus.write(" +/- %.1f\n" % mi.fastmodel.Nchannel_err, scroll=False) 408 if self.robot.optCond or self.robot.optVar: 409 amp_lbl, std_lbl = ('Amp', 'Variance') if (not mi.fastmodel.IeqFv) else ('Cond', 'CondVar') 410 if mi.fastmodel.IeqFv and mi.fastmodel.iVoltage: 411 if self.robot.optCond: 412 self.outStatus.write('Baseline Amp: ', scroll=False) 413 self.outStatus.write_bold('%.5g' % mi.fastmodel.amp0, scroll=False) 414 self.outStatus.write(' +/- %.5g\n' % mi.fastmodel.amp0_err, scroll=False) 415 if self.robot.optVar: 416 self.outStatus.write('Baseline Variance: ', scroll=False) 417 self.outStatus.write_bold('%.5g' % mi.fastmodel.var0, scroll=False) 418 self.outStatus.write(' +/- %.5g\n' % mi.fastmodel.var0_err, scroll=False) 419 self.outStatus.write('%7s' % 'State', scroll=False) 420 if self.robot.optCond: 421 self.outStatus.write_bold('%14s' % amp_lbl, scroll=False) 422 self.outStatus.write('%14s' % (amp_lbl+' err'), scroll=False) 423 if self.robot.optVar: 424 self.outStatus.write_bold('%14s' % std_lbl, scroll=False) 425 self.outStatus.write('%14s' % (std_lbl+' err'), scroll=False) 426 self.outStatus.write('\n', scroll=False) 427 for c in xrange(mi.fastmodel.Nclass): 428 self.outStatus.write('%7d' % c) 429 if self.robot.optCond: 430 self.outStatus.write_bold('%14.5g' % mi.fastmodel.amp[c], scroll=False) 431 self.outStatus.write('%14.5g' % mi.fastmodel.amp_err[c], scroll=False) 432 if self.robot.optVar: 433 self.outStatus.write_bold('%14.5g' % mi.fastmodel.var[c], scroll=False) 434 self.outStatus.write('%14.5g' % mi.fastmodel.var[c], scroll=False) 435 self.outStatus.write('\n', scroll=False) 436 self.outStatus.write('%10s%7s' % ("Rate From", "To"), scroll=False) 437 self.outStatus.write_bold('%14s' % "k0", scroll=False) 438 self.outStatus.write('%11s' % "+/-", scroll=False) 439 self.outStatus.write_bold('%14s' % 'k1', scroll=False) 440 self.outStatus.write('%11s' % "+/-", scroll=False) 441 self.outStatus.write_bold('%14s' % 'k2', scroll=False) 442 self.outStatus.write('%11s' % "+/-", scroll=False) 443 for mi, source in itertools.izip(ws.param.modelinfo, model_prep.sources): 444 self.outStatus.write('\n', scroll=False) 445 for rate in source.rates: 446 a,b = rate.From, rate.To 447 source.rates[rate.Index, 'k0 std'] = mi.fastmodel.K0_err[a,b] 448 source.rates[rate.Index, 'k1 std'] = mi.fastmodel.K1_err[a,b] 449 source.rates[rate.Index, 'k2 std'] = mi.fastmodel.K2_err[a,b] 450 self.outStatus.write('%10d%7d' % (a, b), scroll=False) 451 self.outStatus.write_bold('%14.5g' % rate.k0, scroll=False) 452 self.outStatus.write('%11.3g' % mi.fastmodel.K0_err[a,b], scroll=False) 453 self.outStatus.write_bold('%14.5g' % rate.k1, scroll=False) 454 self.outStatus.write('%11.3g' % mi.fastmodel.K1_err[a,b], scroll=False) 455 self.outStatus.write_bold('%14.5g' % rate.k2, scroll=False) 456 self.outStatus.write('%11.3g' % mi.fastmodel.K2_err[a,b], scroll=False) 457 self.outStatus.write('\n')
458 - def __onIteration(self, iteration, LL, grads, optNchannel, workspaces, modelinfo, K0s, K1s, K2s, amps, stds, amp0s, std0s, Nchannels):
459 self.LL = LL 460 self.iterations = iteration 461 self.grads = grads 462 for mi, K0, K1, K2, amp, std, amp0, std0, Nchannel in itertools.izip(modelinfo, K0s, K1s, K2s, amps, stds, amp0s, std0s, Nchannels): 463 model = mi.prep.source 464 if not model: 465 continue 466 K012ToModel(K0, K1, K2, model) 467 if model.IeqFv: 468 for c,a in enumerate(amp): 469 model.classes.set(c, 'Cond', a) 470 model.classes.set(c, 'CondStd', std[c]) 471 model.classes.set(0, 'Amp', amp0) 472 model.classes.set(0, 'Std', std0) 473 else: 474 for c,a in enumerate(amp): 475 model.classes.set(c, 'Amp', a) 476 model.classes.set(c, 'Std', std[c]) 477 model.set_channelCount(Nchannel, seal=False) # seal: don't want undo-per-iteration 478 if (self.__Ndata > 1) and optNchannel and (len(modelinfo) == 1): 479 for i, ws in enumerate(workspaces): 480 datafile = qubx.global_namespace.QubX.Data.views[ws.param.data.file_index].file 481 try: 482 cc_ix = datafile.constants.index('Channel Count') 483 datafile.constants[cc_ix, 'Value'] = ws.param.modelinfo[0].fastmodel.Nchannel 484 except KeyError: 485 datafile.constants.append({'Name' : 'Channel Count', 'Value' : ws.param.modelinfo[0].fastmodel.Nchannel}) 486 self.invalidate_controls(False) 487 self.outStatus.write('Iter: %i\tLL: %8.5g\tgrads: %s\n' % (iteration, LL, ', '.join("%8.5g"%g for g in grads))) 488 self.OnIterationMR(iteration, LL, grads, K0s, K1s, amps, stds, amp0s, std0s, Nchannels) 489 self.OnIteration2MR(iteration, LL, grads, K0s, K1s, K2s, amps, stds, amp0s, std0s, Nchannels)
490 - def on_interrupt(self, task, cancel):
491 cancel() # the flag is good enough, while the KeyboardInterrupt can bewilder it 492 for ws in self.robot.workspaces: 493 for mi in ws.param.modelinfo: 494 mi.fastmodel.obj[0].stop_flag = 1
495 - def get_model_prep(self, model=None):
496 mp = super(RatesFace, self).get_model_prep(model) 497 mp.models = [mp.model] 498 mp.sources = [mp.source] if mp.source else [] 499 mp.preps = [mp] # please please please garbage collect despite this cycle 500 mp.set_rates2 = set_modelprep_rates(mp) 501 return mp
502 - def request_data_prep(self, receiver, segs, model_prep, last_data_prep=None, resample=None, resample_std=None):
503 QubX = qubx.global_namespace.QubX 504 view_of_file = dict([(view.file, view) for view in QubX.Data.views]) 505 view_segm = [] 506 vs_by_file = {} 507 for seg in segs: 508 if not (seg.file in vs_by_file): 509 vs = (view_of_file[seg.file], []) 510 view_segm.append(vs) 511 vs_by_file[seg.file] = vs 512 vs_by_file[seg.file][1].append(seg) 513 if not view_segm: 514 if receiver: 515 gobject.idle_add(receiver, DataPrep(segs, [])) 516 return 517 518 resamp = QubX.Modeling.MacroR.resample if (resample is None) else resample 519 resamp_std = QubX.Modeling.MacroR.resample_delta if (resample_std is None) else resample_std 520 521 datas = [] 522 data_prep = DataPrep(segs, datas) 523 self.last_LL = 0.0 524 # segment is divided in chunks, alternating included/excluded (data / no data) 525 # each segment, we request idl of each signal, each chunk 526 # this could involve re-idealization, indefinite time, so result comes via callback 527 # we provide generator instance as callback, repeatedly, to process segments in a loop 528 529 # gobject thread: stimulus robot: 530 # rcv_stims.next() 531 # repeat: 532 # stimulus.call_with_stimulus_idl 533 # yield... 534 # [get(self, ...) returns] 535 # idealize stim if needed 536 # idle_add(rcv_stims.send, ...) 537 # ...process chunks 538 # yield None 539 540 rcv_stims = None # will have instance of receive_stims() 541 def rcv_hack(*args): # can send only one argument into a generator 542 rcv_stims.send(args)
543 def receive_stims(): 544 stim_names = iunion((prep.source or prep.model).variables for prep in model_prep.preps) 545 for dataview, segsrc in view_segm: 546 # re-use L{macro_r_lib.Data} object: (single-file only) 547 if last_data_prep and len(last_data_prep.datas) and (len(view_segm) == 1): 548 data = last_data_prep.datas[0] 549 data.reset(len(stim_names)) 550 else: 551 data = macro_r_lib.Data(len(stim_names), file_index=QubX.Data.views.index(dataview)) # , data_cap_samples) 552 553 sampling_sec = dataview.file.sampling 554 if 'Voltage' in stim_names: 555 data.iVoltage = stim_names.index('Voltage') + 1 556 else: 557 data.iVoltage = 0 558 data.signal_names = ['Current'] + stim_names 559 data.ff_ll_counts = [] 560 561 iseg = -1 562 for seg in segsrc: 563 data.file = seg.file 564 data.iSignal = seg.signal 565 QubX.Modeling.Stimulus.call_with_stimulus_idl(rcv_hack, [(chunk.f, chunk.l) for chunk in seg.chunks], datafile=dataview.file, names=stim_names) 566 stim_names, stimidl = (yield) 567 chunkidl = [ Anon(dwellCounts=[], classeses=[], durationses=[], ampses=[]) for chunk in seg.chunks] 568 missing = False 569 for segs_flcda in stimidl: # for each signal 570 for ichunk, flcda in enumerate(segs_flcda): # each chunk 571 ff, ll, cc, dd, aa = flcda 572 if not len(cc): 573 missing = True 574 chunkidl[ichunk].dwellCounts.append(len(cc)) 575 chunkidl[ichunk].classeses.append(cc) 576 chunkidl[ichunk].durationses.append(dd) 577 chunkidl[ichunk].ampses.append(aa) 578 if missing: 579 continue 580 581 iseg += 1 582 data.append_segment() 583 for ichunk, chunk in enumerate(seg.chunks): 584 if chunk.included: 585 Nd = chunk.n 586 chunkoff = chunk.f - seg.f 587 xx = arange(chunkoff, chunkoff+Nd, dtype='float32') 588 xx *= sampling_sec 589 yy = zeros(shape=(Nd,), dtype='float32') 590 ii = 0 591 for ch in generate_chunk_samples([chunk]): 592 yy[ii:ii+ch.n] = ch.samples 593 ii += ch.n 594 if resamp: 595 means, stds, ff, ll, closest = qubx.fast.data.adaptive_resample(yy, resamp_std) 596 xx = array(xx[closest], dtype='float32', copy=True) 597 yy = array(yy[closest], dtype='float32', copy=True) 598 ff += chunk.f 599 ll += chunk.f 600 Nd = len(means) 601 else: 602 ff = arange(seg.f+chunkoff, seg.f+chunkoff+Nd, dtype='int32') 603 ll = ff 604 data.ff_ll_counts.append( (ff, ll, Nd) ) 605 else: 606 Nd = 0 607 xx = yy = None 608 data.append_chunk(iseg, (chunk.l - chunk.f + 1) * sampling_sec, Nd, xx, yy, None, 609 chunkidl[ichunk].dwellCounts, chunkidl[ichunk].classeses, 610 chunkidl[ichunk].durationses, chunkidl[ichunk].ampses, 611 (not resamp) and sampling_sec or 0.0) 612 datas.append(data) 613 gobject.idle_add(receiver, data_prep) 614 yield None
615 616 rcv_stims = receive_stims() 617 rcv_stims.next()
618 - def __onGPUFail(self):
619 self.chkGPU.set_active(False) 620 self.chkGPU.set_sensitive(False) 621 self.chkGPU.set_tooltip_text('no OpenCL-compatible graphics processor is available')
622 - def wait_for_fits(self):
623 """Waits until all pending work is done on the thread which updates fit curves, then returns.""" 624 fitbot = qubx.global_namespace.QubX.Modeling.MacRates.fitbot if ((not self.recursive_fit) and hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates')) else self.fitbot 625 while fitbot.last_serial != fitbot.serial: 626 qubx.pyenv.env.process_events()
627 - def __capture_robot_settings(self):
628 """Should happen at the same time the modelprep is captured, e.g. at button press""" 629 self.robot.Peq = self.use_Peq 630 self.robot.GPU = self.chkGPU.get_active() 631 self.robot.Gleak = self.Gleak 632 self.robot.Vleak = self.Vleak 633 self.robot.optNchannel = self.opt_Nchannel 634 self.robot.optCond = self.opt_cond 635 self.robot.optVar = self.opt_var 636 self.data.resample = self.resample 637 self.data.resample_std = self.resample_delta 638 self.robot.K012 = self.rates.val 639 self.robot.rates_source = qubx.global_namespace.QubX.Models.file 640 if self.robot.GPU: 641 self.robot.accel = self.accel and (self.accel + int(self.robot.GPU)) or 0 642 else: 643 self.robot.accel = self.accel
644 - def robot_setup(self):
645 model_prep = self.robot.model_prep 646 self.robot.models = model_prep.models 647 self.robot.modelinfo = [Anon(model=prep.model, prep=prep) for prep in model_prep.preps if prep.model.states.size] 648 self.data.model_prep = model_prep 649 650 for mi in self.robot.modelinfo: 651 mi.clazz = ModelToClazz(mi.model) 652 mi.Nclass = int(max(mi.clazz) + 1) if mi.clazz.shape[0] else 0 653 654 datas = self.robot.data = self.data_prep.datas # if Nclass else [] 655 if self.robot.workspaces: 656 for ws in self.robot.workspaces: 657 try: 658 ws.fastmodel.dispose() # work around callback-related memory leak 659 except: 660 pass 661 workspaces = self.robot.workspaces = [] 662 self.__Ndata = reduce(operator.add, (data.Ndata for data in datas), 0) 663 664 for data in datas: 665 stimclasses = data.get_stimclasses() 666 Nstim = data.Nstim 667 Nsig = data.Nsig 668 sc_frac = data.get_stimclass_frac() 669 param = macro_r_lib.Param() 670 param.modelinfo = [] 671 param.data = data 672 param.useVar = True ## 673 param.optNchannel = self.robot.optNchannel 674 param.accel = self.robot.accel 675 676 for im, mi in enumerate(self.robot.modelinfo): 677 fastmodel = ModelToFast(mi.model, data.signal_names, self.robot_on_status) 678 fastmodel.usePeq = self.robot.Peq 679 fastmodel.iVoltage = data.iVoltage 680 fastmodel.Gleak = self.robot.Gleak 681 fastmodel.Vleak = self.robot.Vleak 682 K0, K1, K2, L, V, P = ModelToRateMatricesP(mi.model, data.signal_names) 683 fastmodel.K0[:], fastmodel.K1[:], fastmodel.K2[:] = K0, K1, K2 = self.robot.K012[im] 684 685 if len(datas) > 1: 686 try: 687 fastmodel.Nchannel = qubx.global_namespace.QubX.Data.views[data.file_index].file.constants.get_by_name('Channel Count', 'Value') 688 except: 689 pass # use value from model 690 691 ratesm = qubx.fast.model.StimRates(fastmodel, Nstim, Nsig, stimclasses, sc_frac, 0.0) # skip ratesm.AA generation 692 A, B = mi.model.constraints_kin.get_matrices_p(K0, K1, K2, L, V, P, auto=False) 693 if A != None: 694 ratesm.cns.Ain[:A.shape[0],:A.shape[1]] = A 695 ratesm.cns.Bin[:B.shape[0]] = B.flatten() 696 ratesm.cns.Ncns = B.shape[0] 697 else: 698 ratesm.cns.Ncns = 0 699 700 ampm = qubx.fast.model.StimAmps(fastmodel, Nstim, Nsig, stimclasses, sc_frac) 701 if not self.robot.optCond: 702 for c in xrange(fastmodel.Nclass): 703 ampm.fix_amp(c) 704 if fastmodel.IeqFv: 705 ampm.fix_amp0() 706 if not self.robot.optVar: 707 for c in xrange(fastmodel.Nclass): 708 ampm.fix_var(c) 709 if fastmodel.IeqFv: 710 ampm.fix_var0() 711 param.add_model(fastmodel, ampm, ratesm) 712 param.modelinfo.append(mi) 713 mi.fastmodel = fastmodel 714 mi.Nchannel = fastmodel.Nchannel 715 716 if param.models: 717 param.setup_constraints() 718 workspaces.append(Anon(param=param, data=data, modelinfo=param.modelinfo, L=L, V=V))
719 - def robot_live(self, model_prep=None):
720 if self.__invalid_live and not self.robot.working: 721 if model_prep is None: 722 with self.robot.main_hold: 723 mp = self.model_prep.val 724 else: 725 mp = model_prep 726 self.robot_setup_data(mp, None) 727 LL = self.robot_calculate_score() 728 gobject.idle_add(self.OnLiveLL, LL)
729 - def robot_calculate_score(self, grads=None):
730 LL = 0.0 731 try: 732 self.robot.working = True 733 self.__invalid_live = False 734 if not self.optimizing: 735 self.robot_setup() 736 LL = 0.0 737 for ws in self.robot.workspaces: 738 try: 739 LL += macro_r_lib.mac_ll(ws.param) 740 except: 741 gobject.idle_add(self.outStatus.write, traceback.format_exc() + '\n') 742 LL += -1.0 743 if ws.param.gpu_fail: 744 gobject.idle_add(self.__onGPUFail) 745 self.robot.accel -= 1 746 for ws2 in self.robot.workspaces: 747 ws2.param.accel = self.robot.accel 748 self.robot_queue_fits(0, True) 749 except: 750 self.robot.send_exception() 751 gobject.idle_add(self.__onGotLL, LL) 752 self.robot.working = False 753 return LL
754 - def robot_get_pars(self):
755 """Returns the list of initial parameter values, on worker thread; override this method. 756 Derive parameter values from self.robot.model and/or from your own widgets.""" 757 self.robot_setup() 758 if self.robot.workspaces: 759 pars = [float(x) for x in self.robot.workspaces[0].param.fPars[:self.robot.workspaces[0].param.NfPar]] # get as python array 760 else: 761 pars = [] 762 # fastmodel has room for one channel count, used for first file; append a param for the rest 763 if (len(self.robot.workspaces) > 1) and self.robot.optNchannel and (len(self.robot.models) == 1): 764 pars.extend([1.0] * (len(self.robot.workspaces)-1)) 765 self.__scale_Nchannel = [log(ws.param.Nchannel) for ws in self.robot.workspaces[1:]] 766 return array(pars)
767 - def robot_set_pars(self, pars):
768 if (pars != None) and self.robot.workspaces: 769 NfPar = self.robot.workspaces[0].param.NfPar 770 self.robot.workspaces[0].param.fPars[:NfPar] = pars[:NfPar] 771 self.robot.workspaces[0].param.do_fPars_to_model() 772 if (len(self.robot.workspaces) > 1) and self.robot.optNchannel and (len(self.robot.models) == 1): 773 ccpar = pars[-(len(self.robot.workspaces)-1):] 774 for i, ws in enumerate(self.robot.workspaces[1:]): 775 float_Nchannel = max(1.0, exp(self.__scale_Nchannel[i] * ccpar[i])) 776 ws.param.float_Nchannel = float_Nchannel 777 ws.param.modelinfo[0].fastmodel.Nchannel = int(round(float_Nchannel))
778 - def robot_optimize(self):
779 try: 780 self.robot.working = True 781 self.robot.LL = 0.0 782 self.__last_fits_iter = -1 783 iterations = 0 784 pars = grads = [] 785 Hessian = None 786 self.robot.LL, pars, iterations, grads, Hessian = super(RatesFace, self).robot_optimize() 787 self.robot.workspaces[0].param.calc_std(Hessian) 788 if self.accel and (self.__last_fits_iter < iterations): 789 self.robot_queue_fits(iterations, True) 790 except: 791 self.robot.send_exception() 792 gobject.idle_add(self.__onOptDone, self.robot.LL, iterations, grads, Hessian, self.robot.workspaces, self.robot.model_prep, self.data_prep) 793 self.robot.working = False 794 return self.robot.LL, pars, iterations, grads, Hessian
795 - def robot_on_iteration(self, pars, grads, iterations, score):
796 if self.robot.workspaces[0].param.modelinfo[0].fastmodel.obj[0].stop_flag: 797 raise KeyboardInterrupt() 798 else: 799 self.robot_set_pars(pars) 800 self.robot_queue_fits(iterations) 801 self.robot.LL = score 802 K0s = [array(mi.fastmodel.K0, copy=True) for mi in self.robot.workspaces[0].param.modelinfo] 803 K1s = [array(mi.fastmodel.K1, copy=True) for mi in self.robot.workspaces[0].param.modelinfo] 804 K2s = [array(mi.fastmodel.K2, copy=True) for mi in self.robot.workspaces[0].param.modelinfo] 805 rest = ([array(mi.fastmodel.amp, copy=True) for mi in self.robot.workspaces[0].param.modelinfo], 806 [numpy.sqrt(array(mi.fastmodel.var, copy=True)) for mi in self.robot.workspaces[0].param.modelinfo], 807 [mi.fastmodel.amp0 for mi in self.robot.workspaces[0].param.modelinfo], 808 [sqrt(mi.fastmodel.var0) for mi in self.robot.workspaces[0].param.modelinfo], 809 [mi.fastmodel.Nchannel for mi in self.robot.workspaces[0].param.modelinfo]) 810 gobject.idle_add(self.__onIteration, iterations, - score, grads, self.robot.optNchannel, 811 self.robot.workspaces, self.robot.workspaces[0].param.modelinfo, 812 K0s, K1s, K2s, *rest)
813 - def robot_queue_fits(self, iteration=0, force=False):
814 if (not force) and self.optimizing and self.accel: 815 if (datetime.datetime.now() - self.__last_fits_time).seconds < MIN_SECONDS_BETWEEN_FIT: 816 return 817 if (not self.recursive_fit) and hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates'): 818 MacRates = qubx.global_namespace.QubX.Modeling.MacRates 819 if not MacRates.live: 820 MacRates.live = True 821 MacRates.live = False 822 return 823 self.__last_fits_time = datetime.datetime.now() 824 self.__last_fits_iter = iteration 825 self.fitbot.serial += 1 # shared serial so it updates all files or none 826 for data in self.robot.data: 827 #print 'Ndata:',self.robot.data.Ndata 828 chunks = [] 829 samples = data.get_buffer() 830 Ndata = data.Ndata 831 start = 0 832 for firsts, lasts, count in data.ff_ll_counts: 833 means = numpy.array(samples[start:start+count, 0], copy=True) 834 stds = numpy.sqrt(array(samples[start:start+count, 1])) # what if some are neg/nan? 835 #means = array([sample.fit for sample in samples[start:start+count]], dtype='float32') 836 #stds = array([sqrt(sample.fit_var) for sample in samples[start:start+count]], dtype='float32') 837 chunks.append((firsts, lasts, means, stds)) 838 start += count 839 self.fitbot.do(self.fitbot_set_fits, data.file, data.iSignal, chunks, self.fitbot.serial)
840 - def robot_on_status(self, s):
841 gobject.idle_add(self.outStatus.write, s+"\n")
842 - def fitbot_set_fits(self, datafile, iSignal, chunks, serial):
843 # separate thread to wait for gui to finish drawing, discard stale iterations' fit curves, apply latest 844 if serial == self.fitbot.serial: 845 gobject.idle_add(self.do_set_fits, datafile, iSignal, chunks) 846 self.fitbot.last_serial = serial
847 - def do_set_fits(self, datafile, iSignal, chunks):
848 fits = datafile.fits[iSignal].idl 849 fits.clear() 850 for ff, ll, mm, ss in chunks: 851 fits.set_fit(ff, ll, mm, ss) 852 datafile.OnChangeFits(datafile, iSignal)
853
854 855 -def set_modelprep_rates(mp):
856 # override of ModelPrep.set_rates2 for multi_model 857 def set_rates(*K012s): 858 for prep, K012 in itertools.izip(mp.preps, K012s): 859 prep.K0, prep.K1, prep.K2 = K012 860 K012ToModel(prep.K0, prep.K1, prep.K2, prep.model)
861 return set_rates 862 863 864 865 ## [x] start at equilibrium P Gleak: [_________0.0_] [x] live LL: [___-33952.5_] ## 866 ## [x] resample by delta [___1.0_] Vleak: [_________0.0_] [[ Get LL ]] ## 867 ## |==========================================================| Optimize: ## 868 ## |MacroR started... | [x] channel count ## 869 ## | 1: -33129 | [x] amp ## 870 ## | 2: -32981 | [x] var ## 871 ## | 3: -32360 | [x] rates (grayed out; tooltip fix via constraints) 872 ## | 4: -32110 | ## 873 ## |==========================================================| [ DFP |v] [ Optimize ] ## 874 875 # repair or lose pascal max*ll; harmonize with QuB # was: fix reduceconstraints in pascal (what else has changed?) 876 # G/Vleak sensitive only for IeqFv models? 877 878 # other outputs (tabular results?) 879 # averagecount, variance of current 880 # fast/slow numgrad? 881 # useVar always? 882