1 """Panel which shows an open qub model.
2
3 Copyright 2008-2014 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 import qubx.accept
23 import qubx.data_types
24 import qubx.dataGTK
25 import qubx.fit
26 import qubx.treeGTK
27 import qubx.pyenv
28 import numpy
29 import scipy
30 import scipy.optimize
31
32 from qubx.toolspace import *
33 from qubx.GTK import *
34 from qubx.util_types import *
35 from qubx.model import *
36 import qubopt.model
37 from math import *
38 import gc
39 import qubx.settings
40 from qubx.settings import Property, Propertied
41 expm = scipy.linalg.matfuncs.expm
42
43 any = __builtins__['any']
44
45 Tools = ToolRegistry()
46
47
48 COLOR_STATE_BORDER = ('modelGTK.state.border', (0, 0, 0, 1))
49 ColorInfo[COLOR_STATE_BORDER[0]].label = 'Model state border'
50 COLOR_STATE_LABEL = ('modelGTK.state.label', (1, 1, 1, 1))
51 ColorInfo[COLOR_STATE_LABEL[0]].label = 'Model state label'
52 COLOR_RATE = ('modelGTK.rate', (0, 0, 0, 1))
53 ColorInfo[COLOR_RATE[0]].label = 'Model rate label'
54 COLOR_EFFECTIVE_RATE = ('modelGTK.rate.effective', (.5,0,.5,1))
55 ColorInfo[COLOR_EFFECTIVE_RATE[0]].label = 'Model rate label (effective K)'
56 COLOR_BG = ('modelGTK.bg', (1, 1, .75, 1))
57 ColorInfo[COLOR_BG[0]].label = 'Model background'
58
59 COLOR_CHANCOUNT = ('modelGTK.channelCount', (1,1,1,1))
60 ColorInfo[COLOR_CHANCOUNT[0]].label = 'Model channel count'
61 COLOR_BUTTON = ('modelGTK.button', (0,0,0,.85))
62 ColorInfo[COLOR_BUTTON[0]].label = 'Model layer background'
63 COLOR_BUTTON_TEXT = ('modelGTK.button.text', LAYER_FG[1])
64 ColorInfo[COLOR_BUTTON_TEXT[0]].label = 'Model layer text'
65 COLOR_BUTTON_GRAY = ('modelGTK.button.gray', (1,1,1,.4))
66 ColorInfo[COLOR_BUTTON_GRAY[0]].label = 'Model layer text inactive'
67 COLOR_BUTTON_HOVER = ('modelGTK.button.hover', (1, .9, .9, 1))
68 ColorInfo[COLOR_BUTTON_HOVER[0]].label = 'Model button text mouseover'
69 COLOR_QUESTION_BORDER = ('modelGTK.button.border', (1,0,0,1))
70 ColorInfo[COLOR_QUESTION_BORDER[0]].label = 'Model hint "?" border'
71
72 CLICK_SLOP = 2
73
74 MENU_TIMEOUT = 360
75 MENU_STRAYDIUS = 2
76
77 IGNORE_MENU_RELEASE = 5
81 """Returns the Euclidean distance between (x1, y1) and (x2, y2)."""
82 return sqrt((x2 - x1)**2 + (y2 - y1)**2)
83
85 """Returns a function f; every time you call f() it returns x."""
86 return lambda: x
87
90 """Displays and edits one L{qubx.model.QubModel}.
91
92 @ivar deftool: the standard mouse controller
93 """
94
95 __explore_featured = ['controls', 'models_table', 'cBG', 'layExtMenu', 'mnuExt', 'subExtMenu', 'subNotebook', 'nbPicture', 'nbPictureNO',
96 'nbQ', 'nbA', 'nbPeq', 'nbKeq', 'nbDelG', 'nbMFP', 'nbQflux', 'layIeqFv', 'chkIeqFv', 'layVrev', 'lblVrev',
97 'layChannelCount', 'lblChannelCount', 'layRedo', 'lblRedo', 'layUndo', 'lblUndo', 'deftool', 'dispose',
98 'file', 'get_stimulus', 'do_scroll_channelCount', 'copy_image', 'copy_tree', 'paste_tree',
99 'find_state', 'find_rate', 'add_state', 'del_state', 'add_rate', 'del_rate', 'make_start_state',
100 'set_state_color', 'add_constraint_kin']
101
102 - def __init__(self, models_table=None, menu_width=32):
103 """@param models_table: the L{qub.model.QubModels} table-like object."""
104 ToolSpace.__init__(self, can_focus=True)
105 self.controls = self.layerset = LayerSet()
106 self.models_table = models_table
107 self.set_size_request(200, 70)
108 self.ref = Reffer()
109 self.OnDraw += self.ref(self._onDrawModel), 'ModelView.onDrawModel'
110 self._file = None
111 self._stateRects = []
112 self._rateRectss = []
113 self._rateRegions = []
114 self.cBG = COLOR_BG
115 self.__data_stimulus = None
116 self.optimizing = False
117
118 self.layExtMenu = Layer(menu_width+4.5, -4, 3, 3, COLOR_CLEAR)
119 self.add_layer(self.layExtMenu)
120 self.mnuExt = gtk.Menu()
121 self.subExtMenu = SubLayer_MenuLines(self.mnuExt, 'Modeling Tools', self.ref(self.__onPopupExt), x=.25, y=.25, w=2.5, h=2.5)
122 self.layExtMenu.add_sublayer(self.subExtMenu)
123 self.layNotebook = Layer(menu_width+7.5, -4, 3, 3, COLOR_CLEAR)
124 self.add_layer(self.layNotebook)
125 self.subNotebook = qubx.notebookGTK.SubLayer_Notebook(x=.25, y=.25, w=2.5, h=2.5)
126 self.layNotebook.add_sublayer(self.subNotebook)
127 self.nbPicture = qubx.notebookGTK.NbPicture(mnu_caption='Model image', global_name='QubX.Models.view.nbPicture', get_shape=self.__nb_get_shape, draw=self.__nb_draw)
128 self.subNotebook.items.append(self.nbPicture)
129 self.nbPictureNO = qubx.notebookGTK.NbPicture(mnu_caption='Model image, no overlays', global_name='QubX.Models.view.nbPictureNO', get_shape=self.__nb_get_shape, draw=self.__nb_draw_no_overlays)
130 self.subNotebook.items.append(self.nbPictureNO)
131 self.nbQ = qubx.notebook.NbTable('Q matrix', 'QubX.Models.view.nbQ', lambda: 'Q matrix',
132 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Q'),
133 self.__nb_get_row_Q, get_type=lambda: float)
134 self.subNotebook.items.append(self.nbQ)
135 self.nbA = qubx.notebook.NbTable('A matrix', 'QubX.Models.view.nbA', lambda: 'A matrix',
136 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('A'),
137 self.__nb_get_row_A, get_type=lambda: float)
138 self.subNotebook.items.append(self.nbA)
139 self.nbPeq = qubx.notebook.NbTable('Peq', 'QubX.Models.view.nbPeq', lambda: 'Peq',
140 self.__nb_get_vector_shape, lambda: self.__nb_get_matrix_headers('Peq'),
141 self.__nb_get_col_Peq, get_type=lambda: float)
142 self.subNotebook.items.append(self.nbPeq)
143 self.nbKeq = qubx.notebook.NbTable('Eq constants', 'QubX.Models.view.nbKeq', lambda: 'Eq constants',
144 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Eq constants'),
145 self.__nb_get_row_Eq_constants, get_type=lambda: float)
146 self.subNotebook.items.append(self.nbKeq)
147 self.nbDelG = qubx.notebook.NbTable('Delta free energy', 'QubX.Models.view.nbDelG', lambda: 'Delta free energy (log10)',
148 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Delta free energy (log10)'),
149 self.__nb_get_row_dfe, get_type=lambda: float)
150 self.subNotebook.items.append(self.nbDelG)
151 self.nbMFP = qubx.notebook.NbTable('Mean first passage', 'QubX.Models.view.nbMFP', lambda: 'Mean first passage',
152 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Mean first passage'),
153 self.__nb_get_row_mfp, get_type=lambda: float)
154 self.subNotebook.items.append(self.nbMFP)
155 self.nbQflux = qubx.notebook.NbTable('Qflux', 'QubX.Models.view.nbQflux', lambda: 'Qflux',
156 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Qflux'),
157 self.__nb_get_row_Qflux, get_type=lambda: float)
158 self.subNotebook.items.append(self.nbQflux)
159 self.subNotebook.menuitems.append(build_menuitem('Picture options...', self.ref(lambda item: qubx.global_namespace.QubX.Models.nbPicturePrefs.run())))
160
161 self.layIeqFv = Layer(-16, 1, 15, 2, COLOR_BUTTON)
162 self.chkIeqFv = SubLayer_Check(x=1, y=.5, w=1, h=1)
163 self.chkIeqFv.OnToggle += self.ref(self._onToggleIeqFv)
164 self.layIeqFv.add_sublayer(self.chkIeqFv)
165 self.layIeqFv.add_sublayer(SubLayer_Label('I = f(V)', 0, 1, action=self.ref(self._onClickIeqFv), x=2, y=0, w=7, h=2))
166 self.layIeqFv.add_sublayer(SubLayer_Label('?', 0, 1, action=self.ref(self._onClickAboutIeqFv), x=12.5, y=.25, w=1.5, h=1.5,
167 border=1, cBorder=COLOR_QUESTION_BORDER, color=COLOR_BUTTON_TEXT, hover_color=COLOR_BUTTON_HOVER))
168 self.add_layer(self.layIeqFv)
169 self.layVrev = Layer(-16, 3, 15, 2, COLOR_BUTTON)
170 self.layVrev.add_sublayer(SubLayer_Label('Vrev [mV]:', -1, 1, action=self.ref(self._onClickVrev), x=2.5, y=.5, w=6.5, h=1))
171 self.lblVrev = SubLayer_Label('0.0', 0, 1, color=COLOR_CHANCOUNT, action=self.ref(self._onClickVrev), x=9, y=.5, w=6, h=1)
172 self.layVrev.add_sublayer(self.lblVrev)
173 self._showing_vRev = False
174 self.layChannelCount = Layer(-20, -4, 19, 3, COLOR_BUTTON)
175 self.layChannelCount.add_sublayer(SubLayer_Label('Channel Count: ', 1, 1, action=self.ref(self._onClickChannelCount),
176 x=2, y=.5, w=11, h=2, scroll=self.ref(self.__onScrollChannelCount)))
177 self.lblChannelCount = SubLayer_Label('1', 0, 1, color=COLOR_CHANCOUNT, action=self.ref(self._onClickChannelCount),
178 x=13, y=.5, w=5, h=2, scroll=self.ref(self.__onScrollChannelCount))
179 self.layChannelCount.add_sublayer(self.lblChannelCount)
180 self.add_layer(self.layChannelCount)
181
182 self.layRedo = Layer(-8, -8, 7, 3, COLOR_BUTTON)
183 self.lblRedo = SubLayer_Label('Redo', 0, 1, color=COLOR_BUTTON_TEXT, hover_color=COLOR_BUTTON_HOVER, action=self.ref(self._onClickRedo),
184 x=1, y=.5, w=5, h=2)
185 self.layRedo.add_sublayer(self.lblRedo)
186 self.add_layer(self.layRedo)
187
188 self.layUndo = Layer(-8, -12, 7, 3, COLOR_BUTTON)
189 self.lblUndo = SubLayer_Label('Undo', 0, 1, color=COLOR_BUTTON_TEXT, hover_color=COLOR_BUTTON_HOVER, action=self.ref(self._onClickUndo),
190 x=1, y=.5, w=5, h=2)
191 self.layUndo.add_sublayer(self.lblUndo)
192 self.add_layer(self.layUndo)
193
194 self.defer_scriptable_scroll = qubx.pyenv.DeferredScriptableScroll()
195
196 self.deftool = self.tool = QubModelDefaultTool()
197
198 self.chkBalanceLoops = gtk.CheckButton('')
199 self.chkBalanceLoops.set_tooltip_text("Maintains detailed balance on all cycles")
200 self.chkBalanceLoops.connect('toggled', self.__onToggleBalanceLoops)
201
202 try:
203 qubx.global_namespace.QubX.DataSource.OnChangeSel += self.ref(self._onChangeSel)
204 except:
205 pass
209
210 file = property(lambda self: self._file, lambda self, x: self.set_file(x))
212 if self._file == x: return
213 tool = self.tool
214 self.tool = None
215 if self._file:
216
217 self._file.OnSetChannelCount -= self.ref(self._onSetChannelCount)
218 self._file.OnSetIeqFv -= self.ref(self._onSetIeqFv)
219 self._file.OnSetVRev -= self.ref(self._onSetVRev)
220 self._file.OnSetK0Format -= self.ref(self._onChange)
221 self._file.OnSetK1Format -= self.ref(self._onChange)
222 self._file.undoStack.OnChange -= self.ref(self._onUndoChange)
223 self._file.states.OnSet -= self.ref(self._onSetState)
224 self._file.rates.OnSet -= self.ref(self._onSetRate)
225 self._file.classes.OnSet -= self.ref(self._onChange)
226 self._file.states.OnInsert -= self.ref(self._onChange)
227 self._file.rates.OnAfterInsert -= self.ref(self._onInsertedRate)
228 self._file.classes.OnInsert -= self.ref(self._onChange)
229 self._file.states.OnRemoving -= self.ref(self._onChange)
230 self._file.rates.OnRemoving -= self.ref(self._onRemovingRate)
231 self._file.classes.OnRemoving -= self.ref(self._onChange)
232 self._file = x
233 if self._file:
234 if not ('Peq' in self._file.states.fields):
235 self._file.states.add_field('Peq', 0.0, acceptNothing, '%.3f', '')
236 self._file.states.add_field('TimeConstant', 0.0, acceptNothing, '%.3f', 'ms')
237
238 self._file.OnSetChannelCount += self.ref(self._onSetChannelCount), 'ModelsView.onSetChannelCount'
239 self._file.OnSetIeqFv += self.ref(self._onSetIeqFv)
240 self._file.OnSetVRev += self.ref(self._onSetVRev)
241 self._file.OnSetK0Format += self.ref(self._onChange)
242 self._file.OnSetK1Format += self.ref(self._onChange)
243 self._file.undoStack.OnChange += self.ref(self._onUndoChange), 'ModelsView.onUndoChange'
244 self._file.states.OnSet += self.ref(self._onSetState), 'ModelView.onChange [states]'
245 self._file.rates.OnSet += self.ref(self._onSetRate)
246 self._file.classes.OnSet += self.ref(self._onChange), 'ModelView.onChange [classes]'
247 self._file.states.OnInsert += self.ref(self._onChange), 'ModelView.onChange [states insert]'
248 self._file.rates.OnAfterInsert += self.ref(self._onInsertedRate)
249 self._file.classes.OnInsert += self.ref(self._onChange), 'ModelView.onChange [classes insert]'
250 self._file.states.OnRemoving += self.ref(self._onChange), 'ModelView.onChange [states del]'
251 self._file.rates.OnRemoving += self.ref(self._onRemovingRate), 'ModelView.onChange [rates del]'
252 self._file.classes.OnRemoving += self.ref(self._onChange), 'ModelView.onChange [classes del]'
253 self._onSetChannelCount(self.file, self.file.channelCount)
254 self._onSetIeqFv(self.file, self.file.IeqFv)
255 self._onSetVRev(self.file, self.file.vRev)
256 self._onUndoChange(self.file.undoStack)
257 for i in xrange(self._file.rates.size):
258 self.__compute_k(i)
259 self.chkBalanceLoops.set_active(self.file.balance_loops)
260 self.tool = tool
261 self.redraw_canvas()
273 if field in ('x', 'y', 'Label', 'Class'):
274 self._onChange()
275 - def _onSetRate(self, i, field, val, prev, undoing):
290 stimulus = self.__data_stimulus
291 states = self._file.states
292 Q = qubx.model.ModelToQ(self._file, stimulus)
293 Peq = qubx.model.QtoPe(Q)
294 ksum = [0.0] * states.size
295 for rate in self._file.rates:
296 ksum[rate.From] += Q[rate.From,rate.To]
297 for i in xrange(states.size):
298 states[i, 'Peq'] = Peq[i]
299 states[i, 'TimeConstant'] = ksum[i] and (1e3 / ksum[i])
307 stimulus = self.__data_stimulus
308 nstate = self._file.states.size
309 rr = self._file.rates
310 rate = rr.get(r, 'k0')
311 if stimulus:
312 lig, volt, press = rr.get(r, 'Ligand'), rr.get(r, 'Voltage'), rr.get(r, 'Pressure')
313 if lig and stimulus.has_key(lig):
314 rate *= stimulus[lig]
315 if volt and stimulus.has_key(volt):
316 rate *= exp(stimulus[volt] * rr.get(r, 'k1'))
317 if press and stimulus.has_key(press):
318 rate *= exp(stimulus[press] * rr.get(r, 'k2'))
319 if not rate:
320 rate = MIN_TOTAL_RATE
321 rr[r, 'K'] = rate
326 if self.models_table:
327 self.models_table.select(self.models_table.entries.index(self.file), 'Channel Count', self)
336 if file == self.file:
337 self.chkIeqFv.active = val
338 if val and not self._showing_vRev:
339 self.add_layer(self.layVrev)
340 self._showing_vRev = True
341 elif self._showing_vRev and not val:
342 self.remove_layer(self.layVrev)
343 self._showing_vRev = False
357 if self.models_table:
358 self.models_table.select(self.models_table.entries.index(self.file), 'vRev', self)
376 - def copy_image(self, width, height, zoom, copy_overlays=True):
377 pixmap = gdk.Pixmap(self.window, width, height, -1)
378 ctx = pixmap.cairo_create()
379 ctx.scale(zoom, zoom)
380 zw, zh = int(round(width/zoom)), int(round(height/zoom))
381 self.draw_to_context(ctx, zw, zh, overlay=copy_overlays)
382 del ctx
383 pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, width, height)
384 pixbuf.get_from_drawable(pixmap, gdk.colormap_get_system(), 0, 0, 0, 0, -1, -1)
385 clipboard = gtk.clipboard_get('CLIPBOARD')
386 clipboard.set_image(pixbuf)
387 del pixmap
388 del pixbuf
389 qubx.pyenv.env.gc_collect_on_idle()
390 self.redraw_canvas()
397 pp = qubx.global_namespace.QubX.Models.nbPicturePrefs
398 if not pp.running:
399 pp = None
400 if pp and not pp.color:
401 context.set_source_rgba(1, 1, 1, 1)
402 else:
403 context.set_source_rgba(* self.appearance.color(self.cBG))
404 context.paint()
405 if not self.file: return
406
407 ss = self.file.states
408 if pp:
409 Pr = [1.0] * len(ss)
410 if pp.states_proportional_Peq:
411 Pr = QtoPe(ModelToQ(self.file, self.get_stimulus( qubx.global_namespace.QubX.Data.view )))
412 else:
413 Pr = [ss[i, 'Pr'] for i in xrange(len(ss))]
414 if not any(p for p in Pr):
415 Pr = [1.0/max(1, len(ss))] * len(ss)
416 sp_min, sp_max = pp.states_proportional_min_size/100, pp.states_proportional_max_size/100
417 sp_d = sp_max - sp_min
418 if pp.states_proportional_log:
419 sp_log_lo, sp_log_hi = pp.states_proportional_min, pp.states_proportional_max
420 sp_log = lambda x: log(min(0.999999, max(sp_log_lo/10, x)))
421 sp_loglo = sp_log(sp_log_lo)
422 sp_log_scale = sp_d / (sp_log(sp_log_hi) - sp_loglo)
423 sp_factor = [sqrt(sp_log_scale*(sp_log(p) - sp_loglo) + sp_min) for p in Pr]
424 else:
425 sp_factor = [sqrt(p*sp_d + sp_min) for p in Pr]
426
427 if pp.state_font:
428 context.set_font_size(pp.state_font_size)
429
430 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M')
431 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
432
433 arrowhead_l = 1.5*width
434 sw2 = 1.5*width
435 if pp and not pp.state_boxes:
436 sw2 /= 2
437 self._stateRects = []
438 sw2max = sw2
439 for i in xrange(ss.size):
440 x = ss.get(i, 'x') * w / 100.0
441 y = ss.get(i, 'y') * h / 100.0
442 context.set_source_rgba(* self.appearance.color(COLOR_CLASS(ss.get(i, 'Class'))))
443 if pp and pp.states_proportional:
444 context.set_font_size(max(1, int(round(pp.state_font_size*sp_factor[i]))))
445 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M')
446 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
447 sw2i = 1.5*width
448 if not pp.state_boxes:
449 sw2i /= 2
450 if not pp.states_proportional_font:
451 context.set_font_size(pp.state_font and pp.state_font_size or self.appearance.font_size)
452 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M')
453 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
454 else:
455 sw2i = sw2
456 sw2max = max(sw2i, sw2max)
457
458 self._stateRects.append(RotRect(x-sw2i-CLICK_SLOP, y-sw2i-CLICK_SLOP, x+sw2i+CLICK_SLOP, y+sw2i+CLICK_SLOP, state=i))
459 context.rectangle(x-sw2i, y-sw2i, 2*sw2i, 2*sw2i)
460 if (pp is None) or (pp.color and pp.state_fill):
461 context.fill_preserve()
462 if (pp is None) or pp.state_boxes:
463 if pp and not pp.color:
464 context.set_source_rgba(0, 0, 0, 1)
465 else:
466 context.set_source_rgba(* self.appearance.color(COLOR_STATE_BORDER))
467 context.stroke()
468 else:
469 context.new_path()
470 if pp and not pp.color:
471 context.set_source_rgba(0, 0, 0, 1)
472 else:
473 context.set_source_rgba(* self.appearance.color(COLOR_STATE_LABEL))
474 if (pp is None) or pp.state_labels:
475 lbl = ss.get(i, 'Label') or str(i)
476 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(lbl)
477 context.move_to(x - xbearing - width/2, y - ybearing - height/2)
478 context.show_text(lbl)
479 sr = distance(0, 0, sw2max, sw2max)
480
481 self._rateRectss = []
482 self._rateRegions = []
483 def setcolor_rate():
484 if pp and not pp.color:
485 context.set_source_rgba(0, 0, 0, 1)
486 else:
487 context.set_source_rgba(* self.appearance.color(COLOR_RATE))
488 def setcolor_rate_eff():
489 if pp and not pp.color:
490 context.set_source_rgba(0, 0, 0, 1)
491 else:
492 context.set_source_rgba(* self.appearance.color(COLOR_EFFECTIVE_RATE))
493 setcolor_rate()
494 if pp and pp.rate_font:
495 context.set_font_size(pp.rate_font_size)
496 else:
497 context.set_font_size(self.appearance.font_size)
498 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M')
499 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
500
501 lw = [2]
502 if pp and pp.arrows_proportional:
503 ap_log = pp.arrows_proportional_log
504 ap_min, ap_max = pp.arrows_proportional_min, pp.arrows_proportional_max
505 if ap_log:
506 ap_min, ap_max = log(ap_min), log(ap_max)
507 def set_line_width(k):
508
509 if ap_log:
510 k = log(k)
511 lw[0] = 1.0 + (fheight/2.0 - 2.0) * max(0.0, min(1.0, (k - ap_min) / (ap_max - ap_min)))
512 context.set_line_width(lw[0])
513 else:
514 context.set_line_width(2)
515
516 rr = self.file.rates
517 for i in xrange(rr.size):
518 rects = {}
519 regions = collections.defaultdict()
520 a, b = rr.get(i, 'From'), rr.get(i, 'To')
521 ax, bx = ss.get(a, 'x'), ss.get(b, 'x')
522 ay, by = ss.get(a, 'y'), ss.get(b, 'y')
523 up = ax <= bx
524 if not up:
525 a,b,ax,bx,ay,by = b,a,bx,ax,by,ay
526 x0, y0 = ax * w/100.0, ay * h/100.0
527 x1, y1 = bx * w/100.0, by * h/100.0
528 theta = atan2(y1 - y0, x1 - x0)
529 rad = distance(x0, y0, x1, y1)
530 lA, lB = sr + 1, rad - sr - 1
531 lM = (lA + lB) / 2.0
532
533 context.save()
534 context.translate(x0, y0)
535 context.rotate(theta)
536 if pp and pp.arrows_proportional:
537 set_line_width(rr.get(i, 'k0'))
538 if up:
539
540 context.save()
541 context.translate(lB, -lw[0]/2-1-fheight/4.0)
542 context.rotate(pi/4)
543 context.translate(0, lw[0]/2)
544 context.move_to(-arrowhead_l-lw[0], 0)
545 context.line_to(0, 0)
546 context.stroke()
547 context.restore()
548
549 context.move_to(lA, -1-fheight/4.0)
550 context.line_to(lB, -1-fheight/4.0)
551 context.stroke()
552 else:
553
554 context.save()
555 context.translate(lA, lw[0]/2+1+fheight/4.0)
556 context.rotate(pi/4)
557 context.translate(0, -lw[0]/2)
558 context.move_to(0, 0)
559 context.line_to(arrowhead_l+lw[0], 0)
560 context.stroke()
561 context.restore()
562
563 context.move_to(lA, 1+fheight/4.0)
564 context.line_to(lB, 1+fheight/4.0)
565 context.stroke()
566 else:
567 if up:
568
569 context.save()
570 context.translate(lB, -lw[0]/2)
571 context.rotate(pi/4)
572 context.translate(0, lw[0]/2)
573 context.move_to(-arrowhead_l-lw[0], 0)
574 context.line_to(0, 0)
575 context.stroke()
576 context.restore()
577
578 context.move_to(lM, 0)
579 context.line_to(lB, 0)
580 context.stroke()
581 else:
582
583 context.save()
584 context.translate(lA, lw[0]/2)
585 context.rotate(pi/4)
586 context.translate(0, -lw[0]/2)
587 context.move_to(0, 0)
588 context.line_to(arrowhead_l+lw[0], 0)
589 context.stroke()
590 context.restore()
591
592 context.move_to(lA, 0)
593 context.line_to(lM, 0)
594 context.stroke()
595 rects['line'] = RotRect(lA-CLICK_SLOP, -2-CLICK_SLOP, lB+CLICK_SLOP, 2+CLICK_SLOP, x0, y0, theta, rate=i, part="line")
596
597 p = rr.get(i, 'Ligand') and 'L' or ''
598 q = rr.get(i, 'Voltage') and 'V' or ''
599 press = rr.get(i, 'Pressure') and 'P' or ''
600 fmt = rr.get(i, 'Format') or self.file.k0format
601 fmt = fmt and qubx.accept.acceptFormat(fmt) or rr.format['k0']
602 k0 = qubx.accept.proper_minus(fmt(rr.get(i, 'k0')))
603 pq = '%s%s' % (p,q)
604 fmt = self.file.k1format and qubx.accept.acceptFormat(self.file.k1format) or rr.format['k1']
605 k1 = qubx.accept.proper_minus(fmt(rr.get(i, 'k1')))
606 k2 = qubx.accept.proper_minus(fmt(rr.get(i, 'k2')))
607 eff_k = fmt(rr.get(i, 'K'))
608 if up:
609 ty0 = -fheight/2 - fdescent + 1
610 ty1 = ty0 - fheight
611 else:
612 ty0 = fheight/2 + fascent + 1
613 ty1 = ty0 + fheight
614 if pq or press:
615 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(eff_k)
616 tx0 = rad/2 - width/2
617 if (pp is None) or (pp.rate_labels and pp.effective_rate_labels):
618 setcolor_rate_eff()
619 context.move_to(tx0 - xbearing, ty1)
620 context.show_text(eff_k)
621 setcolor_rate()
622 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(pq)
623 tx0 = rad/2 - width/2
624 if (pp is None) or pp.rate_labels:
625 context.move_to(tx0 - xbearing, ty0)
626 context.show_text(pq)
627 rects['flags'] = RotRect(tx0 - 5 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + 5 + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='flags')
628 pq0, pq1 = tx0, tx0 + width
629 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(k0)
630 tx0 = pq0 - width - 5
631 if (pp is None) or pp.rate_labels:
632 context.move_to(tx0 - xbearing, ty0)
633 context.show_text(k0)
634 rects['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k0')
635 regions['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k0')
636 tx0 = pq1 + 5
637 if q:
638 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(k1)
639 if (pp is None) or pp.rate_labels:
640 context.move_to(tx0 - xbearing, ty0)
641 context.show_text(k1)
642 rects['k1'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k1')
643 regions['k1'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k1')
644 tx0 += width + self.appearance.emsize
645 if press:
646 pk2 = "%s %s" % (press, k2)
647 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(pk2)
648 if (pp is None) or pp.rate_labels:
649 context.move_to(tx0 - xbearing, ty0)
650 context.show_text(pk2)
651 rects['k2'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k2')
652 regions['k2'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k2')
653 regions.default_factory = default_static(RotRect(pq0 - width, ty0 + ybearing, pq1 + width, ty0 + ybearing + height, x0, y0, theta, rate=i))
654 else:
655 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(k0)
656 tx0 = rad/2 - width/2
657 if (pp is None) or pp.rate_labels:
658 context.move_to(tx0 - xbearing, ty0)
659 context.show_text(k0)
660 rects['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part="k0")
661 regions['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part="k0")
662 regions.default_factory = default_static(RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part="k0"))
663
664 context.restore()
665 self._rateRectss.append(rects)
666 self._rateRegions.append(regions)
668 for r in self._stateRects:
669 if r.contains(px, py):
670 return r.state, r
671 return None, None
673 for rr in self._rateRectss:
674 for r in rr.itervalues():
675 if r.contains(px, py):
676 return r.rate, r.part, r
677 return None, "", None
698 hdrs = [""] * self.file.states.size
699 hdrs[0] = lbl
700 return hdrs
708 Q = self.__nb_Q()
709 Keq = numpy.identity(Q.shape[0])
710 for i in xrange(Q.shape[0]):
711 for j in xrange(i+1,Q.shape[0]):
712 if Q[i,j] and Q[j,i]:
713 Keq[i,j] = Q[i,j] / Q[j,i]
714 Keq[j,i] = 1.0 / Keq[i,j]
715 return Keq
717 Q = array(self.__nb_Q())
718 return [float(x) for x in Q[r,:].flatten()]
720 A = array(self.__nb_A())
721 return [float(x) for x in A[r,:].flatten()]
723 Peq = array(QtoPe( self.__nb_Q() )).flatten()
724 return [float(x) for x in Peq]
726 Keq = array(self.__nb_Keq())
727 return [float(x) for x in Keq[r,:].flatten()]
729 Keq = self.__nb_Keq()
730 delG = numpy.zeros(shape=Keq.shape)
731 for i in xrange(Keq.shape[0]):
732 for j in xrange(Keq.shape[1]):
733 if Keq[i,j]:
734 delG[i,j] = numpy.log10(Keq[i,j])
735 return [float(x) for x in delG[r,:].flatten()]
745 Q = self.__nb_Q()
746 Peq = QtoPe(Q)
747 W = numpy.matrix(numpy.diag(Peq))
748 Qflux = W * Q
749 return [float(x) for x in array(Qflux[r,:]).flatten()]
757 - def add_rate(self, from_state, to_state):
791
1206
1232
1305
1342
1385
1492 f, l, I, S = self.__measure(left, right)
1493 cls_sel = self.cls_now
1494 V = None
1495 done = False
1496 passno = None
1497 iterate = False
1498 if self.model.file.IeqFv:
1499 if not (self.V_index is None):
1500 f, l, V, Vs = self.__measure(left, right, self.V_index)
1501 else:
1502 V = self.V_const
1503 if self.cls > 0:
1504 done = True
1505 elif self.cls == 0:
1506 if self.passno == 0:
1507 passno = 1
1508 else:
1509 done = True
1510 elif self.cls == -1:
1511 iterate = True
1512 else:
1513 if self.cls >= 0:
1514 done = True
1515 else:
1516 iterate = True
1517 self.__show_confirm("Class %i: %.3g +/- %.3g %s" % (cls_sel, I, S, not (V is None) and ("at %.3g mV" % V) or ""),
1518 DeferredSet(cls_sel, I, S, V, f, l, done, passno, iterate))
1526 if self.model.file.IeqFv:
1527 if 0 in self.meas:
1528 m0 = self.meas[0]
1529 if len(m0) == 1:
1530 I0, S0 = m0.values()[0]
1531 else:
1532 V0, V1 = m0.keys()
1533 mean0, std0 = m0[V0]
1534 mean1, std1 = m0[V1]
1535 if std0 < std1:
1536 V0, mean0, std0, V1, mean1, std1 = V1, mean1, std1, V0, mean0, std0
1537 dV0 = 1e-3 * (V0 - clsVrev(0))
1538 G = (mean0 - mean1) / (V0 - V1)
1539 I0 = mean0 - dV0 * G
1540 Gs = sqrt(std0**2 - std1**2) / (V0 - V1)
1541 S0 = sqrt(std0**2 - dV0 * Gs)
1542 classes.set(0, 'Cond', G)
1543 classes.set(0, 'CondStd', Gs)
1544 classes.set(0, 'Amp', I0)
1545 classes.set(0, 'Std', S0)
1546 else:
1547 I0, S0 = [classes.get(0, x) for x in ('Amp', 'Std')]
1548 for c in self.meas.iterkeys():
1549 if c == 0: continue
1550 for V, m in self.meas[c].iteritems():
1551 I, S = m
1552 dV = 1e-3 * (V - clsVrev(c))
1553 classes.set(c, 'Cond', (I - I0) / dV)
1554 classes.set(c, 'CondStd', sqrt(max(0, S**2 - S0**2)) / dV)
1555 if (len(self.meas) > 1) and any(len(meas) > 1 for meas in self.meas.itervalues()):
1556 II, SS, DV, CC = [], [], [], []
1557 for c,m in self.meas.iteritems():
1558 for v in m.iterkeys():
1559 i, s = m[v]
1560 II.append(i)
1561 SS.append(s)
1562 DV.append(1e-3*(v - clsVrev(c)))
1563 CC.append(c)
1564 GG = [classes.get(c, 'Cond') for c in xrange(classes.size)]
1565 GS = [classes.get(c, 'CondStd') for c in xrange(classes.size)]
1566 GG, GS, I0, S0 = FitIeqFv(II, SS, DV, CC, GG, GS, I0, S0)
1567 classes.set(0, 'Amp', I0)
1568 classes.set(0, 'Std', max(0.0, S0))
1569 for c in set(CC):
1570 classes.set(c, 'Cond', GG[c])
1571 classes.set(c, 'CondStd', max(0.0, GS[c]))
1572 if self.meas:
1573 classes.select(sorted(self.meas.keys())[0], 'Cond', sender=self)
1574 else:
1575 for c, mean_std in self.meas.iteritems():
1576 mean, std = mean_std
1577 classes.set(c, 'Amp', mean)
1578 classes.set(c, 'Std', std)
1579 if len(self.meas):
1580 classes.select(sorted(self.meas.keys())[0], 'Amp', sender=self)
1581 self.model.file.undoStack.seal_undo('Grab')
1582 try:
1583 self.model.remove_layer(self.legend)
1584 except:
1585 pass
1586 gobject.idle_add(self.space.set_layerset, self.prev_layerset)
1587 gobject.idle_add(self.space.set_tool, self.prev_tool)
1745
1757
1766
1769 """Returns True if the point (x, y) is within the rect r=(x0, y0, w, h)."""
1770 return (r[0] <= x < (r[0]+r[2])) and (r[1] <= y < (r[1]+r[3]))
1771
1772 -def FitIeqFv(II, SS, DV, CC, GG, GS, I0, S0):
1773 """Finds the baseline current and per-class conductance which best fit current/voltage measurements.
1774
1775 @param II: list of current-mean measurements [pA]
1776 @param SS: list of current-standard-deviation measurements [pA]
1777 @param DV: list of (V - Vrev) measurements [Volts]
1778 @param CC: list of the model class of each measurement
1779 @param GG: list of starting values for conductance, per model class [pS]
1780 @param GS: list of starting values for conductance standard deviation [pS]
1781 @param I0: mean baseline current [pA]
1782 @param S0: standard deviation of baseline current [pA]
1783 @returns: (GG, GS, I0, S0) after nonlinear least squares fitting
1784 """
1785 ncls = len(CC)
1786 fit = qubx.fit.Simplex_LM_Fitter(max_iter=500)
1787
1788 Y = numpy.array(list(II)+list(SS), dtype=numpy.float32)
1789 X = numpy.array(list(DV)+list(DV), dtype=numpy.float32)
1790 C = list(CC) + list(CC)
1791 class VCurve(qubx.fit.BaseCurve):
1792 """params are alternating elements of GG and GS, followed by I0 and S0 (GG[0], GS[0], GG[1], GS[1], ..., I0, S0)."""
1793 def __init__(self):
1794 qubx.fit.BaseCurve.__init__(self)
1795 self.params = [str(i) for i in xrange(2*(ncls+1))]
1796 self.lo = [UNSET_VALUE for i in xrange(2*(ncls+1))]
1797 self.hi = [UNSET_VALUE for i in xrange(2*(ncls+1))]
1798 self.can_fit = [(((i/2)>=ncls) or ((i/2) in CC)) for i in xrange(2*(ncls+1))]
1799 def eval(self, param_vals, xx, vvv=[]):
1800 return numpy.array([(param_vals[-2] + xx[i]*param_vals[2*C[i]]) for i in xrange(len(xx)/2)] +
1801 [sqrt(param_vals[-1]**2 + (xx[i]*param_vals[2*C[i]+1])**2) for i in xrange(len(xx)/2, len(xx))])
1802 param_vals = reduce(lambda x,y: x+list(y), itertools.izip(GG, GS), [])
1803 param_vals += [I0, S0]
1804 fp, ssr, iter, ff = fit(VCurve(), param_vals, X, Y)
1805 return (fp[:-2:2], fp[1:-2:2], fp[-2], fp[-1])
1806
1809 """Modal window with settings for 'copy model image'."""
1811 CopyDialog.__init__(self, 'Copy model image', parent)
1812 line = pack_item(gtk.HBox(True), self.vbox)
1813 pack_label('Zoom:', line, expand=True)
1814 self.txtZoom = pack_item(NumEntry(1.0, format='%.3g', width_chars=11), line, expand=True)
1815 self.chkOverlays = pack_check('overlays', self.vbox, active=True)
1816 - def run(self, view):
1817 response = CopyDialog.run(self, view)
1818 self.copy_zoom = self.txtZoom.value
1819 self.copy_overlays = self.chkOverlays.get_active()
1820 return response
1821
1822 TheCopyDialog = QubModelCopyDialog()
1827 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
1828 self.set_title('QUB Express - About I=f(V), conductance, and voltage')
1829 self.connect('delete_event', self.__onDelete)
1830 scr = gtk.ScrolledWindow()
1831 scr.set_size_request(500, 600)
1832 scr.show()
1833 self.txt = gtk.TextView()
1834 self.txt.set_editable(False)
1835 scr.add(self.txt)
1836 self.txt.show()
1837 self.add(scr)
1838 out = TextViewAppender(self.txt)
1839 out.write(""" About I=f(V), conductance, and Voltage
1840
1841 This checkbox chooses how to model current (I) from the ion channel.
1842
1843 In either case, the baseline is defined by the Amp and Std of Class 0
1844 (in the first row of the Classes table). Amp and Std have units of pA --
1845 the same at all voltages.
1846
1847 baseline = Amp[0]
1848 baseline_std = Std[0]
1849
1850 When "I = f(V)" is un-checked, the unitary (single-channel) current
1851 doesn't vary with voltage. The current in class i is calculated as:
1852
1853 unitary_amp[i] = amp[i] - baseline_amp
1854 unitary_std[i] = sqrt(std[i]**2 - baseline_std**2)
1855
1856 When "I = f(V)" is checked, the unitary current is calculated from the
1857 conductance, in pS:
1858
1859 delta_v = (Voltage - vRev) * 1e-3 # converting from mV to V
1860 I = delta_v * conductance
1861
1862 "Voltage" is the name of a stimulus signal, or in the Constants table.
1863 Its units are mV. "vRev" is the reversal potential, in the Models table,
1864 also in units of mV. The conductance distribution is in the Classes table,
1865 in the columns "Cond" and "CondStd":
1866
1867 unitary_amp[i,v] = delta_v * Cond[i]
1868 unitary_std[i,v] = delta_v * CondStd[i]
1869
1870 You can override vRev, per class, in the Classes table.
1871
1872 """)
1874 self.hide()
1875 return True
1876
1877 TheIeqFvAboutBox = IeqFvAboutBox()
1881 gtk.Dialog.__init__(self, "QUB Express - Model Merge", qubx.GTK.get_active_window(), gtk.DIALOG_MODAL,
1882 buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
1883 self.set_size_request(600, 250)
1884 self.set_default_response(gtk.RESPONSE_ACCEPT)
1885 pack_label('Model Merge builds the cartesian product of two or more models, with constrained rates.', self.vbox)
1886 self.lstModels = pack_item(qubx.GTK.CountList(['Count', 'Model']), self.vbox, expand=True)
1887 for view in qubx.global_namespace.QubX.Models.views:
1888 if view.file.group == 0:
1889 count = 0
1890 elif 0 < view.file.channelCount < 6:
1891 count = view.file.channelCount
1892 else:
1893 count = 1
1894 self.lstModels.append(count, view.file.path)
1896 if gtk.RESPONSE_ACCEPT == gtk.Dialog.run(self):
1897 return self.lstModels.get_entries()
1898 return []
1899
1900 ModelMerge_index = 0
1901
1902 -def ModelMerge(item=None, count_paths=None, merged_name=None):
1938
1939 Tools.register('other', 'Model Merge...', ModelMerge)
1945
1951
1957
1958 Tools.register('other', 'Copy model', CopyModelTree)
1959 Tools.register('other', 'Paste model', PasteModelTree)
1960 Tools.register('other', 'Paste as new model', PasteNewModel)
1961
1962
1963 @Propertied(Property('color', 1, 'False for grayscale'),
1964 Property('auto_size', 1, 'False to use width, height'),
1965 Property('width', 900, 'Output width if not auto_size'),
1966 Property('height', 600, 'Output height if not auto_size'),
1967 Property('rate_labels', 1, 'True to write numbers along arrows'),
1968 Property('effective_rate_labels', 1, 'True to write effective K above stimulus-dependent rates'),
1969 Property('rate_font', 0, 'True to override system font size'),
1970 Property('rate_font_size', 16, 'Size of rate font, if rate_font is True'),
1971 Property('arrows_proportional', 0, 'True to draw arrows wider for faster rates'),
1972 Property('arrows_proportional_log', 1, 'False to scale arrow width linearly with rate'),
1973 Property('arrows_proportional_min', 0.1, 'Rate corresponding to narrowest arrow width'),
1974 Property('arrows_proportional_max', 10000, 'Rate corresponding to widest arrow width'),
1975 Property('state_labels', 1, 'True to write numbers or labels of states in boxes'),
1976 Property('state_font', 0, 'True to override system font size'),
1977 Property('state_font_size', 16, 'Size of state font, if state_font is True'),
1978 Property('state_boxes', 1, 'True to draw rectangles around states'),
1979 Property('state_fill', 1, 'True to color the background of state rectangles'),
1980 Property('states_proportional', 0, 'True to draw states in proportion to occupancy probability'),
1981 Property('states_proportional_log', 0, 'True: logarithmic, False: linear scale'),
1982 Property('states_proportional_min', 1e-5, 'occupancy corresponding to smallest state size'),
1983 Property('states_proportional_max', 1.0, 'occupancy corresponding to largest state size'),
1984 Property('states_proportional_Peq', 1.0, "True: equilibrium; False: States table's Pr column"),
1985 Property('states_proportional_min_size', 50.0, 'Percent of ordinary state size corresponding to 0.0 occupancy'),
1986 Property('states_proportional_max_size', 150.0, 'Percent of ordinary state size corresponding to 1.0 occupancy'),
1987 Property('states_proportional_font', 1, 'False to vary only the state box size'))
1989 __explore_featured = ['global_name', 'running', 'run']
1990 - def __init__(self, parent, global_name=""):
1991 gtk.Dialog.__init__(self, 'Model - Notebook Picture Output Options', parent, gtk.DIALOG_MODAL, buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
1992 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1993 gtk.STOCK_APPLY, gtk.RESPONSE_APPLY))
1994 self.__ref = Reffer()
1995 self.global_name = global_name
1996 self.propertied_connect_settings('ModelNbPicture')
1997 self.set_default_response(gtk.RESPONSE_ACCEPT)
1998 self.running = False
1999 h = pack_item(gtk.HBox(), self.vbox)
2000 chk = pack_check('Color', h, expand=True)
2001 self.propertied_connect_check('color', chk)
2002 h = pack_item(gtk.HBox(), self.vbox)
2003 chk = pack_check('Auto size', h)
2004 self.propertied_connect_check('auto_size', chk)
2005 pack_label('or Width:', h)
2006 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h)
2007 self.propertied_connect_NumEntry('width', txt)
2008 pack_label('Height:', h)
2009 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h)
2010 self.propertied_connect_NumEntry('height', txt)
2011 h = pack_item(gtk.HBox(), self.vbox)
2012 chk = pack_check('Rate labels', h, expand=True)
2013 self.propertied_connect_check('rate_labels', chk)
2014 h = pack_item(gtk.HBox(), self.vbox)
2015 chk = pack_check('Effective K rate labels (for stimulus-dependent rates)', h, expand=True)
2016 self.propertied_connect_check('effective_rate_labels', chk)
2017 h = pack_item(gtk.HBox(), self.vbox)
2018 chk = pack_check('Rate font size:', h)
2019 self.propertied_connect_check('rate_font', chk)
2020 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h)
2021 self.propertied_connect_NumEntry('rate_font_size', txt)
2022 h = pack_item(gtk.HBox(), self.vbox)
2023 chk = pack_check('Arrow width proportional to rate', h)
2024 self.propertied_connect_check('arrows_proportional', chk)
2025 chk = pack_check('log', h)
2026 self.propertied_connect_check('arrows_proportional_log', chk)
2027 pack_label('in range from:', h)
2028 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h)
2029 self.propertied_connect_NumEntry('arrows_proportional_min', txt)
2030 pack_label('to:', h)
2031 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h)
2032 self.propertied_connect_NumEntry('arrows_proportional_max', txt)
2033 h = pack_item(gtk.HBox(), self.vbox)
2034 chk = pack_check('State labels', h, expand=True)
2035 self.propertied_connect_check('state_labels', chk)
2036 h = pack_item(gtk.HBox(), self.vbox)
2037 chk = pack_check('State font size:', h)
2038 self.propertied_connect_check('state_font', chk)
2039 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h)
2040 self.propertied_connect_NumEntry('state_font_size', txt)
2041 h = pack_item(gtk.HBox(), self.vbox)
2042 chk = pack_check('State area proportional to occupancy probability', h)
2043 self.propertied_connect_check('states_proportional', chk)
2044 chk = pack_check('vary font size', h)
2045 self.propertied_connect_check('states_proportional_font', chk)
2046 chk = pack_check('use Peq', h)
2047 chk.set_tooltip_text("use equilibrium probability (otherwise, use States table's Pr column)")
2048 self.propertied_connect_check('states_proportional_Peq', chk)
2049 h = pack_item(gtk.HBox(), self.vbox)
2050 tip = "as a percentage of ordinary state size"
2051 pack_label('scale % from', h)
2052 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.1f', width_chars=6), h)
2053 txt.set_tooltip_text(tip)
2054 self.propertied_connect_NumEntry('states_proportional_min_size', txt)
2055 pack_label('to', h)
2056 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.1f', width_chars=6), h)
2057 txt.set_tooltip_text(tip)
2058 self.propertied_connect_NumEntry('states_proportional_max_size', txt)
2059 chk = pack_check('log in range from', h)
2060 chk.set_tooltip_text("on a logarithmic scale (otherwise, scale them linearly in 0..1)")
2061 self.propertied_connect_check('states_proportional_log', chk)
2062 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.2g', width_chars=6), h)
2063 self.propertied_connect_NumEntry('states_proportional_min', txt)
2064 pack_label('to', h)
2065 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.2g', width_chars=6), h)
2066 self.propertied_connect_NumEntry('states_proportional_max', txt)
2067 h = pack_item(gtk.HBox(), self.vbox)
2068 chk = pack_check('State boxes', h, expand=True)
2069 self.propertied_connect_check('state_boxes', chk)
2070 h = pack_item(gtk.HBox(), self.vbox)
2071 chk = pack_check('State background fill', h, expand=True)
2072 self.propertied_connect_check('state_fill', chk)
2073 h = pack_item(gtk.HBox(), self.vbox)
2074 pack_label('(preview output in the Models panel)', h)
2075 pack_item(qubx.settingsGTK.PresetsMenu('ModelNbPicture', qubx.global_namespace.QubX.appname), h, at_end=True)
2096
2099 names = []
2100 vals = []
2101 for k,v in stim.iteritems():
2102 names.append(k)
2103 try:
2104 v[0]
2105 vals.append(v)
2106 except:
2107 vals.append([v])
2108 for vv in itertools.product(*vals):
2109 yield dict((names[i], v) for i,v in enumerate(vv))
2110
2119
2123
2129
2131 """Calculates equilibrium probability at various combinations of stimulus.
2132 @param modelview: e.g. QubX.Models.view
2133 @param stim: dict(stim_name -> value or list of values); will evaluate the cartesian product of all values
2134 @param idx: Index of class or state
2135 @param calc: interpret idx as class (default), or as state with calc=iter_Pe_of_state
2136 @return: list of dict, with entries for out_name and each stim_name, corresponding to cartesian product of all requested conditions
2137 """
2138 def pack(Pe, values):
2139 values[out_name] = Pe
2140 return values
2141 return [pack(*item) for item in calc(modelview, idx, **stim)]
2142
2144 def to_minimize(p):
2145 Pe, values = calc(modelview, idx, **{stim_name:p[0]}).next()
2146 return (Pe - 0.5)**2
2147 p = [1.0]
2148 result = scipy.optimize.minimize(to_minimize, p)
2149 if result.success:
2150 return result.x[0]
2151 else:
2152 print result.message
2153 return None
2154