myokit 1.37.5__py3-none-any.whl → 1.39.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. myokit/__init__.py +5 -0
  2. myokit/_datablock.py +6 -5
  3. myokit/_expressions.py +6 -1
  4. myokit/_model_api.py +44 -18
  5. myokit/_myokit_version.py +1 -1
  6. myokit/_parsing.py +8 -2
  7. myokit/_sim/cvodessim.py +26 -0
  8. myokit/formats/__init__.py +37 -0
  9. myokit/formats/ansic/_ewriter.py +1 -1
  10. myokit/formats/axon/_abf.py +43 -9
  11. myokit/formats/cellml/v1/__init__.py +5 -5
  12. myokit/formats/cellml/v1/_api.py +220 -122
  13. myokit/formats/cellml/v1/_parser.py +91 -87
  14. myokit/formats/cellml/v1/_writer.py +13 -6
  15. myokit/formats/cellml/v2/__init__.py +5 -8
  16. myokit/formats/cellml/v2/_api.py +182 -106
  17. myokit/formats/cellml/v2/_parser.py +68 -64
  18. myokit/formats/cellml/v2/_writer.py +7 -3
  19. myokit/formats/heka/_patchmaster.py +71 -14
  20. myokit/formats/mathml/_parser.py +106 -67
  21. myokit/gui/source.py +18 -12
  22. myokit/lib/hh.py +21 -37
  23. myokit/tests/test_cellml_v1_api.py +227 -33
  24. myokit/tests/test_cellml_v1_parser.py +48 -17
  25. myokit/tests/test_cellml_v1_writer.py +14 -4
  26. myokit/tests/test_cellml_v2_api.py +132 -114
  27. myokit/tests/test_cellml_v2_parser.py +31 -1
  28. myokit/tests/test_cellml_v2_writer.py +8 -1
  29. myokit/tests/test_datalog.py +17 -0
  30. myokit/tests/test_expressions.py +61 -0
  31. myokit/tests/test_formats.py +99 -0
  32. myokit/tests/test_formats_mathml_content.py +97 -37
  33. myokit/tests/test_formats_python.py +1 -1
  34. myokit/tests/test_model_building.py +2 -0
  35. myokit/tests/test_parsing.py +32 -0
  36. myokit/tests/test_simulation_cvodes.py +10 -4
  37. myokit/tests/test_variable.py +10 -7
  38. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/METADATA +22 -7
  39. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/RECORD +43 -43
  40. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/WHEEL +1 -1
  41. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/entry_points.txt +0 -0
  42. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info/licenses}/LICENSE.txt +0 -0
  43. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/top_level.txt +0 -0
@@ -53,7 +53,7 @@ class CellMLParsingError(myokit.ImportError):
53
53
  if element is not None:
54
54
  try: # pragma: no cover
55
55
  line = str(element.sourceline)
56
- message = 'Error on line ' + line + '. ' + message
56
+ message = f'Error on line {line}. {message}'
57
57
  except AttributeError:
58
58
  pass
59
59
  super().__init__(message)
@@ -89,8 +89,7 @@ class CellMLParser:
89
89
  if element.text is not None:
90
90
  if element.text.strip():
91
91
  raise CellMLParsingError(
92
- 'Text found in ' + self._tag(element, name) + '.',
93
- element)
92
+ f'Text found in {self._tag(element, name)}.', element)
94
93
 
95
94
  # Check child elements
96
95
  allowed = set([self._join(x) for x in children])
@@ -102,17 +101,16 @@ class CellMLParser:
102
101
  # Check for trailing text
103
102
  if child.tail is not None and child.tail.strip():
104
103
  raise CellMLParsingError(
105
- 'Text found in ' + self._tag(element, name)
106
- + ', after ' + self._tag(child) + ' element.',
107
- child)
104
+ f'Text found in {self._tag(element, name)}, after'
105
+ f' {self._tag(child)} element.', child)
108
106
 
109
107
  # Check if allowed
110
108
  if str(child.tag) in allowed:
111
109
  continue
112
110
 
113
111
  raise CellMLParsingError(
114
- 'Unexpected content type in ' + self._tag(element, name)
115
- + ', found element of type ' + self._tag(child) + '.', child)
112
+ f'Unexpected content type in {self._tag(element, name)}, found'
113
+ f' element of type {self._tag(child)}.', child)
116
114
 
117
115
  # Check attributes
118
116
  allowed = set(attributes)
@@ -128,8 +126,8 @@ class CellMLParser:
128
126
  ns, at = split(key)
129
127
  key = self._item(ns, at)
130
128
  raise CellMLParsingError(
131
- 'Unexpected attribute ' + key + ' found in '
132
- + self._tag(element, name) + '.', element)
129
+ f'Unexpected attribute {key} found in'
130
+ f' {self._tag(element, name)}.', element)
133
131
 
134
132
  def _check_id(self, element, obj=None):
135
133
  """
@@ -148,7 +146,7 @@ class CellMLParser:
148
146
 
149
147
  # Check uniqueness
150
148
  if xid in self._ids:
151
- raise CellMLParsingError('Duplicate id "' + xid + '".', element)
149
+ raise CellMLParsingError(f'Duplicate id "{xid}".', element)
152
150
 
153
151
  # Store and return
154
152
  self._ids[xid] = obj
@@ -165,11 +163,11 @@ class CellMLParser:
165
163
  if ns is None:
166
164
  return item
167
165
  elif ns == self._ns:
168
- return 'cellml:' + item
166
+ return f'cellml:{item}'
169
167
  elif ns == cellml.NS_MATHML:
170
- return 'mathml:' + item
168
+ return f'mathml:{item}'
171
169
  else:
172
- return '{' + ns + '}' + item
170
+ return f'{{{ns}}}{item}'
173
171
 
174
172
  def _join(self, element, namespace=None):
175
173
  """
@@ -177,7 +175,7 @@ class CellMLParser:
177
175
  """
178
176
  if namespace is None:
179
177
  namespace = self._ns
180
- return '{' + namespace + '}' + element
178
+ return f'{{{namespace}}}{element}'
181
179
 
182
180
  def parse(self, root):
183
181
  """
@@ -207,7 +205,7 @@ class CellMLParser:
207
205
  parser = etree.XMLParser(remove_comments=True)
208
206
  tree = etree.parse(path, parser=parser)
209
207
  except Exception as e:
210
- raise CellMLParsingError('Unable to parse XML: ' + str(e))
208
+ raise CellMLParsingError(f'Unable to parse XML: {e}')
211
209
 
212
210
  # Parse content
213
211
  return self.parse(tree.getroot())
@@ -222,7 +220,7 @@ class CellMLParser:
222
220
  try:
223
221
  root = etree.fromstring(text)
224
222
  except Exception as e:
225
- raise CellMLParsingError('Unable to parse XML: ' + str(e))
223
+ raise CellMLParsingError(f'Unable to parse XML: {e}')
226
224
 
227
225
  # Parse content
228
226
  return self.parse(root)
@@ -236,8 +234,7 @@ class CellMLParser:
236
234
  name = element.attrib['name']
237
235
  except KeyError:
238
236
  raise CellMLParsingError(
239
- 'Component element must have a name attribute.',
240
- element)
237
+ 'Component element must have a name attribute.', element)
241
238
 
242
239
  # Create component (validates name and checks uniqueness)
243
240
  try:
@@ -259,8 +256,18 @@ class CellMLParser:
259
256
  element, ['variable'], ['name'], name, math=True)
260
257
 
261
258
  # Create variables and set interfaces
259
+ initial_values = []
262
260
  for child in element.findall(self._join('variable')):
263
- self._parse_variable(child, component)
261
+ init = self._parse_variable(child, component)
262
+ if init is not None:
263
+ initial_values.append(init)
264
+
265
+ # Set initial values
266
+ for child, variable, value in initial_values:
267
+ try:
268
+ variable.set_initial_value(value)
269
+ except myokit.formats.cellml.v2.CellMLError as e:
270
+ raise CellMLParsingError(str(e), child)
264
271
 
265
272
  def _parse_connection(self, element, model, connected):
266
273
  """
@@ -288,8 +295,7 @@ class CellMLParser:
288
295
  if c1 == c2:
289
296
  raise CellMLParsingError(
290
297
  'The component_1 and component_2 attributes in a connection'
291
- ' element must be different, got "' + str(c1)
292
- + '" twice.', element)
298
+ f' element must be different, got "{c1}" twice.', element)
293
299
 
294
300
  # Get components
295
301
  try:
@@ -297,22 +303,20 @@ class CellMLParser:
297
303
  except KeyError:
298
304
  raise CellMLParsingError(
299
305
  'A map_components component_1 attribute must refer to a'
300
- ' component in the current model, got "' + str(c1) + '".',
301
- element)
306
+ f' component in the current model, got "{c1}".', element)
302
307
  try:
303
308
  c2 = model.component(c2)
304
309
  except KeyError:
305
310
  raise CellMLParsingError(
306
311
  'A map_components component_2 attribute must refer to a'
307
- ' component in the current model, got "' + str(c2) + '".',
308
- element)
312
+ f' component in the current model, got "{c2}".', element)
309
313
 
310
314
  # Check components are not yet connected
311
315
  if (c1, c2) in connected:
312
316
  raise CellMLParsingError(
313
317
  'Each connection in a model must connect a unique pair of'
314
- ' components, found multiple for "' + c1.name() + '" and "'
315
- + c2.name() + '".', element)
318
+ f' components, found multiple for "{c1.name()}" and'
319
+ f' "{c2.name()}".', element)
316
320
  connected.add((c1, c2))
317
321
  connected.add((c2, c1))
318
322
 
@@ -359,15 +363,13 @@ class CellMLParser:
359
363
  except KeyError:
360
364
  raise CellMLParsingError(
361
365
  'A map_variables variable_1 attribute must refer to a'
362
- ' variable in component_1, got "' + str(v1) + '".',
363
- element)
366
+ f' variable in component_1, got "{v1}".', element)
364
367
  try:
365
368
  v2 = c2.variable(v2)
366
369
  except KeyError:
367
370
  raise CellMLParsingError(
368
371
  'A map_variables variable_2 attribute must refer to a'
369
- ' variable in component_2, got "' + str(v1) + '".',
370
- element)
372
+ f' variable in component_2, got "{v1}".', element)
371
373
 
372
374
  # Connect variables
373
375
  model = c1.model()
@@ -428,8 +430,7 @@ class CellMLParser:
428
430
  except KeyError:
429
431
  raise CellMLParsingError(
430
432
  'A component_ref\'s component attribute must reference a'
431
- ' component in the same model, got "' + component + '".',
432
- element)
433
+ f' component in the same model, got "{component}".', element)
433
434
 
434
435
  # Check allowed content
435
436
  self._check_allowed_content(element, ['component_ref'], ['component'])
@@ -441,9 +442,8 @@ class CellMLParser:
441
442
  if component.parent() is not None:
442
443
  raise CellMLParsingError(
443
444
  'A component can only have a single encapsulation parent:'
444
- ' found ' + str(component) + ' with parents '
445
- + str(component.parent()) + ' and ' + str(parent) + '.',
446
- element)
445
+ f' found {component} with parents {component.parent()} and'
446
+ f' and {parent}.', element)
447
447
 
448
448
  # Set parent (won't raise CellMLErrors)
449
449
  component.set_parent(parent)
@@ -498,8 +498,8 @@ class CellMLParser:
498
498
  units = model.find_units(units)
499
499
  except myokit.formats.cellml.v2.CellMLError:
500
500
  raise CellMLParsingError(
501
- 'Unknown unit "' + str(units) + '" referenced inside a'
502
- ' MathML equation.', element)
501
+ f'Unknown unit "{units}" referenced inside a MathML'
502
+ ' equation.', element)
503
503
 
504
504
  # Create and return
505
505
  return myokit.Number(value, units.myokit_unit())
@@ -516,36 +516,36 @@ class CellMLParser:
516
516
  if ns != cellml.NS_MATHML:
517
517
  raise CellMLParsingError(
518
518
  'The contents of a mathml:math element must be in the'
519
- ' mathml namespace, found "' + str(child.tag) + '" inside '
520
- + str(component) + '.', child)
519
+ f' mathml namespace, found "{child.tag}" inside'
520
+ f' {component}.', child)
521
521
 
522
522
  # If it isn't these it must be an apply
523
523
  if el != 'apply':
524
524
  raise CellMLParsingError(
525
525
  'Unexpected contents in mathml:math. Expecting'
526
- ' mathml:apply but found mathml:' + el + ' inside maths'
527
- ' for ' + str(component) + '.', child)
526
+ f' mathml:apply but found mathml:{el} inside maths for'
527
+ f' {component}.', child)
528
528
 
529
529
  # Parse
530
530
  eq = p.parse(child)
531
531
  if not isinstance(eq, myokit.Equal):
532
532
  raise CellMLParsingError(
533
533
  'Unexpected element in MathML, expecting a list of'
534
- ' equations, got ' + self._tag(child) + '.', child)
534
+ f' equations, got f{self._tag(child)}.', child)
535
535
  lhs, rhs = eq
536
536
 
537
537
  # Check lhs
538
538
  if not isinstance(lhs, myokit.LhsExpression):
539
539
  raise CellMLParsingError(
540
540
  'Invalid expression found on the left-hand side of an'
541
- ' equation: ' + self._dae_message, child)
541
+ f' equation: {self._dae_message}.', child)
542
542
 
543
543
  # Check variable is undefined
544
544
  var = lhs.var()
545
545
  if var.has_equation():
546
546
  raise CellMLParsingError(
547
- 'Overdefined variable: ' + str(var) + ': Two defining'
548
- ' equations.', child)
547
+ f'Overdefined variable: {var}: Two defining equations.',
548
+ child)
549
549
 
550
550
  # Set equations
551
551
  try:
@@ -642,7 +642,7 @@ class CellMLParser:
642
642
  except myokit.formats.cellml.v2.CellMLError as e:
643
643
  raise CellMLParsingError(
644
644
  'Models that take derivatives with respect to more than one'
645
- ' variable are not supported (' + str(e) + ').')
645
+ f' variable are not supported ({e}).')
646
646
 
647
647
  # Read any rdf annotations
648
648
  # TODO: Allow RDF annotations from external files
@@ -697,9 +697,9 @@ class CellMLParser:
697
697
  children = element.findall(self._join('unit'))
698
698
  if not children:
699
699
  warnings.warn(
700
- 'Unable to parse definition for units "' + str(name) + '",'
701
- ' using `dimensionless instead. (Defining new base units is'
702
- ' not supported.)')
700
+ f'Unable to parse definition for units "{name}", using'
701
+ ' `dimensionless instead. (Defining new base units is not'
702
+ ' supported.)')
703
703
 
704
704
  # Parse content
705
705
  myokit_unit = myokit.units.dimensionless
@@ -716,6 +716,10 @@ class CellMLParser:
716
716
  """
717
717
  Parses a variable ``element`` and adds a variable to the given
718
718
  ``component``.
719
+
720
+ If an initial value needs to be set, returns a tuple
721
+ ``(element, variable, initial_value_string)``. Otherwise returns
722
+ ``None``.
719
723
  """
720
724
  # Check name is present
721
725
  try:
@@ -752,13 +756,15 @@ class CellMLParser:
752
756
  ]
753
757
  self._check_allowed_content(element, [], attr, name)
754
758
 
755
- # Set initial value
756
- variable.set_initial_value(
757
- element.attrib.get('initial_value', None))
759
+ # Pass back initial value for delayed processing
760
+ init = element.attrib.get('initial_value', None)
758
761
 
759
762
  except myokit.formats.cellml.v2.CellMLError as e:
760
763
  raise CellMLParsingError(str(e), element)
761
764
 
765
+ if init is not None:
766
+ return (element, variable, init)
767
+
762
768
  def _sort_units(self, element):
763
769
  """
764
770
  Returns all units elements in ``element``, sorted in an order that
@@ -794,14 +800,14 @@ class CellMLParser:
794
800
  # Check doesn't shadow an si unit
795
801
  if name in si_units:
796
802
  raise CellMLParsingError(
797
- 'Units name "' + name + '" overlaps with a predefined name'
798
- ' in ' + self._tag(element) + '.', element)
803
+ f'Units name "{name}" overlaps with a predefined name in'
804
+ f' {self._tag(element)}.', element)
799
805
 
800
806
  # Check for duplicates
801
807
  if name in local_units:
802
808
  raise CellMLParsingError(
803
- 'Duplicate units definition "' + name + '" in '
804
- + self._tag(element) + '.', element)
809
+ f'Duplicate units definition "{name}" in'
810
+ f' {self._tag(element)}.', element)
805
811
  local_units[name] = units
806
812
 
807
813
  # Determine dependencies
@@ -811,8 +817,7 @@ class CellMLParser:
811
817
  dep = unit.attrib['units']
812
818
  except KeyError:
813
819
  raise CellMLParsingError(
814
- 'Unit elements must have a units attribute.',
815
- element)
820
+ 'Unit elements must have a units attribute.', element)
816
821
  deps.add(dep)
817
822
  unresolved[name] = deps
818
823
 
@@ -835,9 +840,8 @@ class CellMLParser:
835
840
  for name, deps in unresolved.items():
836
841
  deps.difference_update(fresh)
837
842
  else:
838
- raise CellMLParsingError(
839
- 'Unable to resolve network of units in '
840
- + self._tag(element) + '.', element)
843
+ raise CellMLParsingError('Unable to resolve network of units'
844
+ f' in {self._tag(element)}.', element)
841
845
  return ordered
842
846
 
843
847
  def _tag(self, element, name=None):
@@ -854,6 +858,6 @@ class CellMLParser:
854
858
  ns, el = split(element.tag)
855
859
  tag = self._item(ns, el)
856
860
  if ns == self._ns and name is not None:
857
- tag += '[@name="' + name + '"]'
861
+ tag += f'[@name="{name}"]'
858
862
  return tag
859
863
 
@@ -279,9 +279,13 @@ class CellMLWriter:
279
279
 
280
280
  # Add initial value
281
281
  if variable is variable.initial_value_variable():
282
- value = myokit.float.str(variable.initial_value()).strip()
283
- if value[-4:] == 'e+00':
284
- value = value[:-4]
282
+ value = variable.initial_value()
283
+ if isinstance(value, myokit.Name):
284
+ value = value.var().name()
285
+ else:
286
+ value = myokit.float.str(value).strip()
287
+ if value[-4:] == 'e+00':
288
+ value = value[:-4]
285
289
  element.attrib['initial_value'] = value
286
290
 
287
291
  def write(self, model):
@@ -1,8 +1,6 @@
1
1
  #
2
2
  # This module reads files in HEKA Patchmaster format.
3
3
  #
4
- # Specifically, it targets the 2x90.2 format.
5
- #
6
4
  # Notes:
7
5
  # - HEKA publishes a lot of information about its file format on its FTP
8
6
  # server: server.hekahome.de
@@ -159,9 +157,10 @@ class PatchMasterFile:
159
157
  except ValueError:
160
158
  pass
161
159
 
162
- if not self._version.startswith('v2x90.2'):
163
- warnings.warn('Only PatchMaster version v2x90.2 is supported.'
164
- f' Attempting to read version {self._version}.')
160
+ if not (self._version.startswith('v2x90.2')
161
+ or self._version.startswith('v2x73')):
162
+ warnings.warn(
163
+ 'Untested PatchMaster format version {self._version}.')
165
164
 
166
165
  # Endianness
167
166
  f.seek(52)
@@ -1021,6 +1020,9 @@ class Series(TreeNode, myokit.formats.SweepSource):
1021
1020
  a.r_series_tau()
1022
1021
  else:
1023
1022
  log.meta[f'{pre}r_series_compensation_enabled'] = 'false'
1023
+ log.meta[f'{pre}current_clamp_holding_current_pA'] = a.i_hold()
1024
+ log.meta[f'{pre}current_clamp_stimulus_gain'] = \
1025
+ a.current_clamp_stimulus_gain()
1024
1026
 
1025
1027
  # Add protocol to meta data
1026
1028
  stimulus = self.stimulus()
@@ -1110,6 +1112,10 @@ class Series(TreeNode, myokit.formats.SweepSource):
1110
1112
  out.append(f' R series compensation: {p} %, {q} us')
1111
1113
  else:
1112
1114
  out.append(' R series compensation: not enabled')
1115
+ # Current-clamp info
1116
+ out.append(f' Current clamp holding current: {a.i_hold()} pA')
1117
+ out.append(' Current clamp stimulus gain: '
1118
+ f'{a.current_clamp_stimulus_gain()} pA/mV')
1113
1119
 
1114
1120
  # Info from first trace
1115
1121
  if len(self) and len(self[0]):
@@ -1491,7 +1497,6 @@ class Trace(TreeNode):
1491
1497
  not perform this zeroing, but it can be applied by setting
1492
1498
  ``ignore_zero_segment`` to ``False``.
1493
1499
  """
1494
-
1495
1500
  if self._data_interleave_size == 0 or self._data_interleave_skip == 0:
1496
1501
  # Read continuous data
1497
1502
  d = np.memmap(self._handle, self._data_type, 'r',
@@ -1666,18 +1671,25 @@ class AmplifierState:
1666
1671
  # Read properties
1667
1672
  i = handle.tell()
1668
1673
 
1669
- # Current gain (V/A)
1674
+ # Current gain (the "gain" in VC mode) (V/A)
1670
1675
  handle.seek(i + 8) # sCurrentGain = 8; (* LONGREAL *)
1671
1676
  self._current_gain = reader.read1('d')
1672
1677
 
1673
1678
  # Holding potential and offsets
1674
1679
  handle.seek(i + 112) # sVHold = 112; (* LONGREAL *)
1675
- self._holding = reader.read1('d')
1680
+ self._vhold = reader.read1('d')
1676
1681
  handle.seek(i + 128) # sVpOffset = 128; (* LONGREAL *)
1677
1682
  self._voff = reader.read1('d')
1678
1683
  handle.seek(i + 136) # sVLiquidJunction = 136; (* LONGREAL *)
1679
1684
  self._ljp = reader.read1('d')
1680
1685
 
1686
+ # Current clamp settings
1687
+ handle.seek(i + 144) # sCCIHold = 144; (* LONGREAL *)
1688
+ self._ihold = reader.read1('d')
1689
+ # Stimulus scaling in current clamp mode (a byte / enum)
1690
+ handle.seek(i + 243) # sCCGain = 243; (* BYTE *)
1691
+ self._cc_gain = reader.read1('b')
1692
+
1681
1693
  # Series resistance and compensation
1682
1694
  handle.seek(i + 40) # sRsFraction = 40; (* LONGREAL *)
1683
1695
  self._rs_fraction = reader.read1('d')
@@ -1730,6 +1742,10 @@ class AmplifierState:
1730
1742
  handle.seek(i + 16) # sF2Bandwidth = 16; (* LONGREAL *)
1731
1743
  self._filter2_freq_both = reader.read1('d') * 1e-3
1732
1744
 
1745
+ # Stimulus filter
1746
+ handle.seek(i + 282) # sStimFilterOn = 282; (* BYTE *)
1747
+ self._stimulus_filter = StimulusFilterSetting(reader.read1('b'))
1748
+
1733
1749
  # Suspect this indicates external filter 2
1734
1750
  #handle.seek(i + 287) # sF2Source = 287; (* BYTE *)
1735
1751
  #self._temp['sF2Source'] = reader.read1('b')
@@ -1752,9 +1768,16 @@ class AmplifierState:
1752
1768
  #handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
1753
1769
  #self._temp['sImon1Bandwidth'] = reader.read1('d')
1754
1770
 
1755
- # Stimulus filter
1756
- handle.seek(i + 282) # sStimFilterOn = 282; (* BYTE *)
1757
- self._stimulus_filter = StimulusFilterSetting(reader.read1('b'))
1771
+ #handle.seek(i + 242) # sCCRange = 242; (* BYTE *)
1772
+ #print('CC range', reader.read1('b'))
1773
+ #handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
1774
+ #print('CC fast on', reader.read1('b'))
1775
+ #handle.seek(i + 286) # sCCFastSpeed = 286; (* BYTE *)
1776
+ #print('CC fast speed', reader.read1('b'))
1777
+ #handle.seek(i + 376) # sCCStimDacScale = 376; (* LONGREAL *)
1778
+ #print('CC stim dac scale', reader.read1('d'))
1779
+ #handle.seek(i + 160) # sCCTrackVHold = 160; (* LONGREAL *)
1780
+ #print('CCTrackVHold', reader.read1('d'))
1758
1781
 
1759
1782
  def c_fast(self):
1760
1783
  """
@@ -1820,9 +1843,37 @@ class AmplifierState:
1820
1843
  """
1821
1844
  return self._cs_range
1822
1845
 
1846
+ def current_clamp_stimulus_gain(self):
1847
+ """
1848
+ Returns the "CC-Gain" setting, in pA/mV.
1849
+
1850
+ For current-clamp measurements, this is one of two values (along with
1851
+ the "CC Stim. Scale", which is currently not provided by this class)
1852
+ that determines the scaling of applied stimuli.
1853
+
1854
+ **This value is provided for information only**: stimuli returned by
1855
+ the :class:`PatchMasterFile` methods are already scaled appropriately.
1856
+ """
1857
+ gain_values = (0.1, 1, 10, 100)
1858
+ try:
1859
+ return gain_values[self._cc_gain]
1860
+ except IndexError: # pragma: no-cover
1861
+ raise NotImplementedError(
1862
+ 'Unexpected value found for current clamp gain'
1863
+ f' ({self._cc_gain}).')
1864
+
1823
1865
  def current_gain(self):
1824
1866
  """
1825
- The gain setting for current measurements, in mV/pA.
1867
+ The gain setting of the "current monitor" output, in mV/pA.
1868
+
1869
+ For voltage-clamp measurements, this is the main "gain" setting used to
1870
+ record currents.
1871
+
1872
+ **This value is provided for information only**: the currents returned
1873
+ by the :class:`PatchMasterFile` methods are already scaled
1874
+ appropriately. The chosen gain value is mainly interesting because it
1875
+ determines the feedback resistor used in the headstage. See the
1876
+ Patchmaster and amplifier manuals for details.
1826
1877
  """
1827
1878
  return self._current_gain * 1e-9
1828
1879
 
@@ -1875,6 +1926,12 @@ class AmplifierState:
1875
1926
  fb = int(fb) if fb == int(fb) else fb
1876
1927
  return f'{self._filter2_type} {fb} kHz combined, {fs} kHz f2-only'
1877
1928
 
1929
+ def i_hold(self):
1930
+ """
1931
+ Returns the current-clamp holding current, in pA.
1932
+ """
1933
+ return self._ihold * 1e12
1934
+
1878
1935
  def ljp(self):
1879
1936
  """
1880
1937
  Returns the liquid junction potential (LJP, in mV) used in the LJP
@@ -1942,12 +1999,12 @@ class AmplifierState:
1942
1999
 
1943
2000
  def v_hold(self):
1944
2001
  """
1945
- Returns the holding potential (in mV).
2002
+ Returns the voltage-clamp holding potential (in mV).
1946
2003
 
1947
2004
  This is the potential last set in the amplifier window, before any
1948
2005
  experiments were run.
1949
2006
  """
1950
- return self._holding * 1e3
2007
+ return self._vhold * 1e3
1951
2008
 
1952
2009
 
1953
2010
  class Filter1Setting(enum.Enum):