myokit 1.36.0__py3-none-any.whl → 1.37.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 (47) hide show
  1. myokit/__init__.py +6 -19
  2. myokit/_datablock.py +45 -55
  3. myokit/_datalog.py +2 -2
  4. myokit/_err.py +26 -3
  5. myokit/_expressions.py +241 -127
  6. myokit/_model_api.py +19 -13
  7. myokit/_myokit_version.py +1 -1
  8. myokit/_sim/cvodessim.c +221 -149
  9. myokit/_sim/jacobian.py +3 -3
  10. myokit/_sim/mcl.h +54 -0
  11. myokit/_sim/openclsim.py +5 -5
  12. myokit/_sim/rhs.py +1 -1
  13. myokit/formats/__init__.py +4 -9
  14. myokit/formats/ansic/_ewriter.py +4 -20
  15. myokit/formats/heka/_patchmaster.py +16 -10
  16. myokit/formats/opencl/_ewriter.py +3 -42
  17. myokit/formats/opencl/template/minilog.py +1 -1
  18. myokit/formats/sympy/_ereader.py +2 -1
  19. myokit/formats/wcp/_wcp.py +3 -3
  20. myokit/gui/datalog_viewer.py +12 -7
  21. myokit/lib/hh.py +3 -0
  22. myokit/lib/markov.py +2 -2
  23. myokit/lib/plots.py +4 -4
  24. myokit/tests/data/formats/wcp-file-empty.wcp +0 -0
  25. myokit/tests/test_datablock.py +10 -10
  26. myokit/tests/test_datalog.py +4 -1
  27. myokit/tests/test_expressions.py +532 -251
  28. myokit/tests/test_formats_ansic.py +6 -18
  29. myokit/tests/test_formats_cpp.py +0 -5
  30. myokit/tests/test_formats_cuda.py +7 -15
  31. myokit/tests/test_formats_easyml.py +4 -9
  32. myokit/tests/test_formats_latex.py +10 -11
  33. myokit/tests/test_formats_matlab.py +0 -8
  34. myokit/tests/test_formats_opencl.py +0 -29
  35. myokit/tests/test_formats_python.py +2 -19
  36. myokit/tests/test_formats_stan.py +0 -13
  37. myokit/tests/test_formats_sympy.py +3 -3
  38. myokit/tests/test_formats_wcp.py +15 -0
  39. myokit/tests/test_lib_hh.py +36 -0
  40. myokit/tests/test_model.py +20 -20
  41. myokit/tests/test_parsing.py +19 -0
  42. {myokit-1.36.0.dist-info → myokit-1.37.0.dist-info}/METADATA +1 -1
  43. {myokit-1.36.0.dist-info → myokit-1.37.0.dist-info}/RECORD +47 -46
  44. {myokit-1.36.0.dist-info → myokit-1.37.0.dist-info}/LICENSE.txt +0 -0
  45. {myokit-1.36.0.dist-info → myokit-1.37.0.dist-info}/WHEEL +0 -0
  46. {myokit-1.36.0.dist-info → myokit-1.37.0.dist-info}/entry_points.txt +0 -0
  47. {myokit-1.36.0.dist-info → myokit-1.37.0.dist-info}/top_level.txt +0 -0
myokit/_sim/mcl.h CHANGED
@@ -651,6 +651,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
651
651
  }
652
652
 
653
653
  // Name
654
+ #ifdef MYOKIT_DEBUG_MESSAGES
655
+ printf(" Querying name\n");
656
+ #endif
654
657
  flag = clGetDeviceInfo(device_id, CL_DEVICE_NAME, bufsize, buffer, NULL);
655
658
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
656
659
  val = PyUnicode_FromString(buffer);
@@ -658,6 +661,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
658
661
  Py_CLEAR(val);
659
662
 
660
663
  // Vendor
664
+ #ifdef MYOKIT_DEBUG_MESSAGES
665
+ printf(" Querying vendor\n");
666
+ #endif
661
667
  flag = clGetDeviceInfo(device_id, CL_DEVICE_VENDOR, bufsize, buffer, NULL);
662
668
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
663
669
  val = PyUnicode_FromString(buffer);
@@ -665,6 +671,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
665
671
  Py_CLEAR(val);
666
672
 
667
673
  // Device version
674
+ #ifdef MYOKIT_DEBUG_MESSAGES
675
+ printf(" Querying device version\n");
676
+ #endif
668
677
  flag = clGetDeviceInfo(device_id, CL_DEVICE_VERSION, bufsize, buffer, NULL);
669
678
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
670
679
  val = PyUnicode_FromString(buffer);
@@ -672,6 +681,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
672
681
  Py_CLEAR(val);
673
682
 
674
683
  // Driver version
684
+ #ifdef MYOKIT_DEBUG_MESSAGES
685
+ printf(" Querying driver version\n");
686
+ #endif
675
687
  flag = clGetDeviceInfo(device_id, CL_DRIVER_VERSION, bufsize, buffer, NULL);
676
688
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
677
689
  val = PyUnicode_FromString(buffer);
@@ -679,6 +691,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
679
691
  Py_CLEAR(val);
680
692
 
681
693
  // Clock speed (MHz)
694
+ #ifdef MYOKIT_DEBUG_MESSAGES
695
+ printf(" Querying clock speed\n");
696
+ #endif
682
697
  flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(buf_uint), &buf_uint, NULL);
683
698
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
684
699
  val = PyLong_FromUnsignedLong(buf_uint);
@@ -686,6 +701,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
686
701
  Py_CLEAR(val);
687
702
 
688
703
  // Global memory (bytes)
704
+ #ifdef MYOKIT_DEBUG_MESSAGES
705
+ printf(" Querying global memory size\n");
706
+ #endif
689
707
  flag = clGetDeviceInfo(device_id, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
690
708
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
691
709
  val = PyLong_FromUnsignedLongLong(buf_ulong);
@@ -693,6 +711,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
693
711
  Py_CLEAR(val);
694
712
 
695
713
  // Local memory (bytes)
714
+ #ifdef MYOKIT_DEBUG_MESSAGES
715
+ printf(" Querying local memory size\n");
716
+ #endif
696
717
  flag = clGetDeviceInfo(device_id, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
697
718
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
698
719
  val = PyLong_FromUnsignedLongLong(buf_ulong);
@@ -700,6 +721,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
700
721
  Py_CLEAR(val);
701
722
 
702
723
  // Const memory (bytes)
724
+ #ifdef MYOKIT_DEBUG_MESSAGES
725
+ printf(" Querying max buffer size\n");
726
+ #endif
703
727
  flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
704
728
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
705
729
  val = PyLong_FromUnsignedLongLong(buf_ulong);
@@ -707,6 +731,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
707
731
  Py_CLEAR(val);
708
732
 
709
733
  // Computing units
734
+ #ifdef MYOKIT_DEBUG_MESSAGES
735
+ printf(" Querying max computing units\n");
736
+ #endif
710
737
  flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(buf_uint), &buf_uint, NULL);
711
738
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
712
739
  val = PyLong_FromUnsignedLong(buf_uint);
@@ -714,6 +741,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
714
741
  Py_CLEAR(val);
715
742
 
716
743
  // Max workgroup size
744
+ #ifdef MYOKIT_DEBUG_MESSAGES
745
+ printf(" Querying max work group size\n");
746
+ #endif
717
747
  flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(buf_size_t), &buf_size_t, NULL);
718
748
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
719
749
  val = PyLong_FromSize_t(buf_size_t);
@@ -721,6 +751,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
721
751
  Py_CLEAR(val);
722
752
 
723
753
  // Max workitem sizes
754
+ #ifdef MYOKIT_DEBUG_MESSAGES
755
+ printf(" Querying max work item sizes\n");
756
+ #endif
724
757
  flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(buf_uint), &buf_uint, NULL);
725
758
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
726
759
  val = PyLong_FromUnsignedLong(buf_uint);
@@ -739,6 +772,9 @@ PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buf
739
772
  Py_CLEAR(items_sizes_tuple);
740
773
 
741
774
  // Maximum size of a kernel parameter
775
+ #ifdef MYOKIT_DEBUG_MESSAGES
776
+ printf(" Querying maximum kernel parameter size\n");
777
+ #endif
742
778
  flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_PARAMETER_SIZE, sizeof(buf_size_t), &buf_size_t, NULL);
743
779
  if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
744
780
  val = PyLong_FromSize_t(buf_size_t);
@@ -834,29 +870,47 @@ mcl_info(void)
834
870
 
835
871
  // Check all platforms
836
872
  for (i=0; i<n_platforms; i++) {
873
+ #ifdef MYOKIT_DEBUG_MESSAGES
874
+ printf("Checking platform %u\n", (unsigned int)i);
875
+ #endif
837
876
 
838
877
  // Create platform dict
839
878
  platform = mcl_info_platform_dict(platform_ids[i], sizeof(buffer), buffer);
840
879
  if (platform == NULL) { Py_DECREF(platforms); return NULL; }
880
+ #ifdef MYOKIT_DEBUG_MESSAGES
881
+ printf(" Obtained platform info dict.\n");
882
+ #endif
841
883
 
842
884
  // Count devices
843
885
  flag = clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_ALL, MCL_MAX_DEVICES, device_ids, &n_devices);
844
886
  if (flag == CL_DEVICE_NOT_FOUND) {
845
887
  n_devices = 0;
888
+ } else if (flag == CL_INVALID_VALUE) {
889
+ // This seems to happen on Mac OS 14.4.1
890
+ n_devices = 0;
846
891
  } else if (mcl_flag(flag)) {
847
892
  Py_DECREF(platforms);
848
893
  return NULL;
849
894
  }
895
+ #ifdef MYOKIT_DEBUG_MESSAGES
896
+ printf(" Found %u devices.\n", (unsigned int)n_devices);
897
+ #endif
850
898
 
851
899
  // Create devices tuple, must decref on exception
852
900
  devices = PyTuple_New((size_t)n_devices);
853
901
 
854
902
  // Add devices
855
903
  for (j=0; j<n_devices; j++) {
904
+ #ifdef MYOKIT_DEBUG_MESSAGES
905
+ printf(" Checking device %u\n", (unsigned int)j);
906
+ #endif
856
907
 
857
908
  // Create device dict, must decref on exception
858
909
  device = mcl_info_device_dict(device_ids[j], sizeof(buffer), buffer);
859
910
  if (device == NULL) { Py_DECREF(platforms); Py_DECREF(devices); return NULL; }
911
+ #ifdef MYOKIT_DEBUG_MESSAGES
912
+ printf(" Obtained device info dict.\n");
913
+ #endif
860
914
 
861
915
  // Add device to devices tuple (steals reference)
862
916
  PyTuple_SetItem(devices, j, device);
myokit/_sim/openclsim.py CHANGED
@@ -534,7 +534,7 @@ class SimulationOpenCL(myokit.CModule):
534
534
  lower, upper = safe_range
535
535
  for dims in myokit._dimco(*self._dims):
536
536
  key = '.'.join([str(x) for x in dims]) + post
537
- ar = np.array(_log[key], copy=False)
537
+ ar = np.asarray(_log[key])
538
538
  i = np.where(
539
539
  (ar < lower)
540
540
  | (ar > upper)
@@ -1080,7 +1080,7 @@ class SimulationOpenCL(myokit.CModule):
1080
1080
  n = len(self._fields) * self._nx * self._ny
1081
1081
  if n:
1082
1082
  field_data = self._fields.values()
1083
- field_data = [np.array(x, copy=False) for x in field_data]
1083
+ field_data = [np.asarray(x) for x in field_data]
1084
1084
  field_data = np.vstack(field_data)
1085
1085
  field_data = list(field_data.reshape(n, order='F'))
1086
1086
  else:
@@ -1342,7 +1342,7 @@ class SimulationOpenCL(myokit.CModule):
1342
1342
  'This method is unavailable when diffusion is disabled.')
1343
1343
 
1344
1344
  # Check the field's size
1345
- gx = np.array(gx, copy=False, dtype=float)
1345
+ gx = np.asarray(gx, dtype=float)
1346
1346
  if len(self._dims) == 1:
1347
1347
  s = self._nx - 1
1348
1348
  if gx.shape != (s, ):
@@ -1360,7 +1360,7 @@ class SimulationOpenCL(myokit.CModule):
1360
1360
  if gy is None:
1361
1361
  raise ValueError(
1362
1362
  'The argument `gy` must be set for 2-d simulations.')
1363
- gy = np.array(gy, copy=False, dtype=float)
1363
+ gy = np.asarray(gy, dtype=float)
1364
1364
  s = (self._ny - 1, self._nx)
1365
1365
  if gy.shape != s:
1366
1366
  raise ValueError(
@@ -1514,7 +1514,7 @@ class SimulationOpenCL(myokit.CModule):
1514
1514
  if not var.is_constant():
1515
1515
  raise ValueError('Only constants can be used for fields.')
1516
1516
  # Check values
1517
- values = np.array(values, copy=False, dtype=float)
1517
+ values = np.asarray(values, dtype=float)
1518
1518
  if len(self._dims) == 1:
1519
1519
  if values.shape != (self._nx, ):
1520
1520
  raise ValueError(
myokit/_sim/rhs.py CHANGED
@@ -153,7 +153,7 @@ class RhsBenchmarker(myokit.CModule):
153
153
  the given benchmarked times.
154
154
  """
155
155
  import numpy as np
156
- times = np.array(times, copy=False)
156
+ times = np.asarray(times)
157
157
  # Remove outliers twice
158
158
  for i in range(0, 2):
159
159
  avg = np.mean(times)
@@ -128,15 +128,7 @@ class ExpressionWriter:
128
128
  ``a**b**c`` is interpreted as ``a**(b**c)``, necessitating a different
129
129
  bracket-adding logic than used in Myokit.
130
130
 
131
- 3. Binary operators are sometimes implemented as n-ary operators. In
132
- Myokit, ``0 == 0 == 0`` is a sequence of two binary operators,
133
- interpreted as ``(0 == 0) == 0``. Because ``(0 == 0)`` evaluates to
134
- ``1``, this expression returns ``0`` (1 does not equal 0). In Python,
135
- the expression ``0 == 0 == 0`` is a ternary (n-ary) operator,
136
- interpreted as ``all_equal(0, 0, 0)``, which evaluates to ``1``. For
137
- languages that use this convention, extra brackets must be added.
138
-
139
- 4. Myokit does not have increment or decrement operators ``--`` and ``++``,
131
+ 3. Myokit does not have increment or decrement operators ``--`` and ``++``,
140
132
  so the expression ``--x`` is interpreted as ``-(-x)``. This is the same
141
133
  in Python. But in C-based languages, this is interpreted as a decrement
142
134
  operator so care must be taken to add extra brackets.
@@ -886,6 +878,9 @@ class SweepSource:
886
878
 
887
879
  Note that a source with zero recorded channels may still report a
888
880
  non-zero number of sweeps if it can provide D/A outputs.
881
+
882
+ Similarly, formats like WCP can report zero sweeps but have a non-zero
883
+ channel count (if no data was recorded).
889
884
  """
890
885
  raise NotImplementedError
891
886
 
@@ -102,23 +102,14 @@ class CBasedExpressionWriter(PythonExpressionWriter):
102
102
 
103
103
  def _ex_not(self, e):
104
104
  # C conditions all have brackets, so don't add more
105
- if isinstance(e[0], (myokit.Condition)):
106
- return f'(!{self.ex(e[0])})'
107
- # But do add more if the user's being silly
108
- return f'(!({self.ex(e[0])}))'
105
+ return f'(!{self.ex(e[0])})'
109
106
 
110
107
  def _ex_if(self, e):
111
- _if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
112
- # If i is not a condtion (which always gets brackets from this writer)
113
- # then add brackets
114
- if not isinstance(e._i, myokit.Condition):
115
- _if = f'({_if})'
116
- return f'({_if} ? {_then} : {_else})'
108
+ return f'({self.ex(e._i)} ? {self.ex(e._t)} : {self.ex(e._e)})'
117
109
 
118
110
  def _ex_piecewise(self, e):
119
111
  # Render ifs; add extra bracket if not a condition (see _ex_if)
120
- _ifs = [self.ex(x) if isinstance(x, myokit.Condition)
121
- else f'({self.ex(x)})' for x in e._i]
112
+ _ifs = [self.ex(x) for x in e._i]
122
113
  _thens = [self.ex(x) for x in e._e]
123
114
 
124
115
  s = []
@@ -200,13 +191,7 @@ class AnsiCExpressionWriter(CBasedExpressionWriter):
200
191
  #def _ex_not(self, e):
201
192
 
202
193
  def _ex_if(self, e):
203
- # Allow _fcond
204
-
205
194
  _if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
206
- # If i is not a condtion (which always gets brackets from this writer)
207
- # then add brackets
208
- if not isinstance(e._i, myokit.Condition):
209
- _if = f'({_if})'
210
195
 
211
196
  # Use if-then-else function?
212
197
  if self._fcond is not None:
@@ -219,8 +204,7 @@ class AnsiCExpressionWriter(CBasedExpressionWriter):
219
204
  # Allow _fcond
220
205
 
221
206
  # Render ifs; add extra bracket if not a condition (see _ex_if)
222
- _ifs = [self.ex(x) if isinstance(x, myokit.Condition)
223
- else f'({self.ex(x)})' for x in e._i]
207
+ _ifs = [self.ex(x) for x in e._i]
224
208
  _thens = [self.ex(x) for x in e._e]
225
209
 
226
210
  s = []
@@ -928,6 +928,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
928
928
  include_da = False
929
929
 
930
930
  # Populate log
931
+ log.set_time_key('time')
931
932
  if join_sweeps:
932
933
  # Join sweeps
933
934
  offsets = (self._sweep_starts_r if use_real_start_times
@@ -964,31 +965,34 @@ class Series(TreeNode, myokit.formats.SweepSource):
964
965
  log.cmeta[name]['unit'] = self._da_units[0]
965
966
 
966
967
  # Add meta data
967
- log.set_time_key('time')
968
+ log.meta['time'] = self._time.strftime(myokit.DATE_FORMAT)
968
969
  a = self.amplifier_state()
970
+ t = self[0][0] if len(self) and len(self[0]) else None
969
971
  log.meta['current_gain_mV_per_pA'] = a.current_gain()
972
+ log.meta['filter1'] = a.filter1()
973
+ if t is not None:
974
+ log.meta['r_pipette_MOhm'] = t.r_pipette()
975
+ log.meta['r_seal_MOhm'] = t.r_seal()
970
976
  log.meta['ljp_correction_mV'] = a.ljp()
971
- log.meta['c_slow_compensation_pF'] = a.c_slow()
977
+ log.meta['voltage_offset_mV'] = a.v_off()
972
978
  if a.c_fast_enabled():
973
979
  log.meta['c_fast_compensation_enabled'] = 'true'
974
980
  log.meta['c_fast_pF'] = a.c_fast()
975
- log.meta['c_fast_tau_micro_s'] = a.c_fast_tau()
981
+ log.meta['c_fast_tau_us'] = a.c_fast_tau()
976
982
  else:
977
983
  log.meta['c_fast_compensation_enabled'] = 'false'
984
+ log.meta['c_slow_pF'] = a.c_slow()
978
985
  log.meta['r_series_MOhm'] = a.r_series()
979
986
  if a.r_series_enabled():
980
987
  log.meta['r_series_compensation_enabled'] = 'true'
981
988
  log.meta['r_series_compensation_percent'] = round(
982
989
  a.r_series_fraction() * 100, 1)
990
+ log.meta['r_series_compensation_tau_us'] = a.r_series_tau()
991
+ if t is not None:
992
+ log.meta['r_series_post_compensation_MOhm'] = \
993
+ t.r_series_remaining()
983
994
  else:
984
995
  log.meta['r_series_compensation_enabled'] = 'false'
985
- if len(self) and len(self[0]):
986
- t = self[0][0]
987
- log.meta['r_pipette_MOhm'] = t.r_pipette()
988
- log.meta['r_seal_MOhm'] = t.r_series()
989
- log.meta['r_series_post_compensation_MOhm'] = \
990
- t.r_series_remaining()
991
- log.meta['c_slow_pF'] = t.c_slow()
992
996
 
993
997
  return log
994
998
 
@@ -1035,6 +1039,8 @@ class Series(TreeNode, myokit.formats.SweepSource):
1035
1039
  if a.r_series_enabled():
1036
1040
  p = round(a.r_series_fraction() * 100, 1)
1037
1041
  out.append(f' R series compensation: {p} %.')
1042
+ p = round(a.r_series_tau(), 1)
1043
+ out.append(f' R series compensation tau: {p} us')
1038
1044
  else:
1039
1045
  out.append(' R series compensation: not enabled')
1040
1046
  if len(self) and len(self[0]):
@@ -31,27 +31,6 @@ class OpenCLExpressionWriter(CBasedExpressionWriter):
31
31
  self._sp = (precision == myokit.SINGLE_PRECISION)
32
32
  self._nm = bool(native_math)
33
33
 
34
- def _exc(self, e):
35
- """Returns ``ex(e)`` if ``e`` is a Condition, else ``ex(e != 0)``."""
36
- # Can be removed after https://github.com/myokit/myokit/issues/1056
37
- if isinstance(e, myokit.Condition):
38
- return self.ex(e)
39
- return self.ex(myokit.NotEqual(e, myokit.Number(0)))
40
-
41
- def _ex_infix_comparison(self, e, op):
42
- """Handles ex() for infix condition operators (==, !=, > etc.)."""
43
- # Can be removed after https://github.com/myokit/myokit/issues/1056
44
- c1 = isinstance(e[0], myokit.Condition)
45
- c2 = isinstance(e[1], myokit.Condition)
46
- if (c1 and c2) or not (c1 or c2):
47
- return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
48
- else:
49
- return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
50
-
51
- def _ex_infix_logical(self, e, op):
52
- # Can be removed after https://github.com/myokit/myokit/issues/1056
53
- return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
54
-
55
34
  #def _ex_name(self, e):
56
35
  #def _ex_derivative(self, e):
57
36
  #def _ex_initial_value(self, e):
@@ -126,25 +105,7 @@ class OpenCLExpressionWriter(CBasedExpressionWriter):
126
105
 
127
106
  #def _ex_and(self, e):
128
107
  #def _ex_or(self, e):
129
-
130
- def _ex_not(self, e):
131
- # Can be removed after https://github.com/myokit/myokit/issues/1056
132
- return f'(!{self._exc(e[0])})'
133
-
134
- def _ex_if(self, e):
135
- # Can be removed after https://github.com/myokit/myokit/issues/1056
136
- _if, _then, _else = self._exc(e._i), self.ex(e._t), self.ex(e._e)
137
- return f'({_if} ? {_then} : {_else})'
138
-
139
- def _ex_piecewise(self, e):
140
- # Can be removed after https://github.com/myokit/myokit/issues/1056
141
- _ifs = [self._exc(x) for x in e._i]
142
- _thens = [self.ex(x) for x in e._e]
143
- s = []
144
- n = len(_ifs)
145
- for _if, _then in zip(_ifs, _thens):
146
- s.append(f'({_if} ? {_then} : ')
147
- s.append(_thens[-1])
148
- s.append(')' * len(_ifs))
149
- return ''.join(s)
108
+ #def _ex_not(self, e):
109
+ #def _ex_if(self, e):
110
+ #def _ex_piecewise(self, e):
150
111
 
@@ -177,7 +177,7 @@ class DataLog(OrderedDict):
177
177
  import numpy as np
178
178
  out = DataLog()
179
179
  for k, d in self.items():
180
- out[k] = np.array(d, copy=False)
180
+ out[k] = np.asarray(d)
181
181
  return out
182
182
 
183
183
  def save_csv(self, filename, pad=None):
@@ -156,7 +156,8 @@ class SymPyExpressionReader:
156
156
  def _ex_abs(self, e):
157
157
  return myokit.Abs(self.ex(e.args[0]))
158
158
 
159
- def _ex_not(self, e):
159
+ def _ex_not(self, e): # pragma: no cover
160
+ # Sympy turns not(a == b) into a != b etc., so can't test!
160
161
  return myokit.Not(self.ex(e.args[0]))
161
162
 
162
163
  def _ex_equal(self, e):
@@ -241,7 +241,7 @@ class WcpFile(myokit.formats.SweepSource):
241
241
 
242
242
  def _channel_id(self, channel_id):
243
243
  """ Checks an int or str channel id and returns a valid int. """
244
- if self._nr == 0: # pragma: no cover
244
+ if self._nr == 0:
245
245
  raise KeyError(f'Channel {channel_id} not found (empty file).')
246
246
 
247
247
  # Handle string
@@ -314,7 +314,7 @@ class WcpFile(myokit.formats.SweepSource):
314
314
 
315
315
  # Create log
316
316
  log = myokit.DataLog()
317
- if self._nr == 0: # pragma: no cover
317
+ if self._nr == 0:
318
318
  return log
319
319
 
320
320
  # Get channel names
@@ -433,7 +433,7 @@ class WcpFile(myokit.formats.SweepSource):
433
433
  return self._nr
434
434
 
435
435
  def records(self):
436
- """ Deprecated alias of :meth:`sweep_count`. """
436
+ """ Deprecated alias of :meth:`record_count`. """
437
437
  # Deprecated since 2023-06-22
438
438
  import warnings
439
439
  warnings.warn(
@@ -641,13 +641,18 @@ class SweepSourceTab(GraphTabWidget):
641
641
  def __init__(self, parent, source):
642
642
  super().__init__(parent)
643
643
 
644
- # Add A/D
645
- for i in range(source.channel_count()):
646
- self._add_graph_tab(source, i)
647
-
648
- # Add D/A
649
- for i in range(source.da_count()):
650
- self._add_graph_tab(source, i, True)
644
+ if source.sweep_count() > 0:
645
+ # Must have this condition to avoid getting exceptions with e.g.
646
+ # the empty WCP file in the test data set.
647
+ # myokit log myokit/tests/data/formats/wcp-file-empty.wcp
648
+
649
+ # Add A/D
650
+ for i in range(source.channel_count()):
651
+ self._add_graph_tab(source, i)
652
+
653
+ # Add D/A
654
+ for i in range(source.da_count()):
655
+ self._add_graph_tab(source, i, True)
651
656
 
652
657
  # Add meta data
653
658
  self._add_meta_tab(source)
myokit/lib/hh.py CHANGED
@@ -298,6 +298,9 @@ class HHModel:
298
298
  '_y[' + k + '] = ' + state.uname() + ' = '
299
299
  + inf + ' + (_y0[' + k + '] - ' + inf
300
300
  + ') * numpy.exp(-_t / ' + tau + ')')
301
+ f.append(
302
+ '_y[' + k + '][_t == 0] = ' + state.uname() + '[_t == 0] = '
303
+ + '_y0[' + k + ']')
301
304
 
302
305
  # Add current calculation
303
306
  if self._current is not None:
myokit/lib/markov.py CHANGED
@@ -1077,7 +1077,7 @@ class AnalyticalSimulation:
1077
1077
  y0 = PI.dot(self._state.reshape((n, 1)))
1078
1078
 
1079
1079
  # Reshape times array
1080
- times = np.array(times, copy=False).reshape((len(times),))
1080
+ times = np.asarray(times).reshape((len(times),))
1081
1081
 
1082
1082
  # Calculate state
1083
1083
  x = P.dot(y0 * np.exp(times * E))
@@ -1223,7 +1223,7 @@ class DiscreteSimulation:
1223
1223
 
1224
1224
  Returns a discretized state ``y`` where ``sum(y) = nchannels``.
1225
1225
  """
1226
- x = np.array(x, copy=False, dtype=float)
1226
+ x = np.asarray(x, dtype=float)
1227
1227
  if (np.abs(1 - np.sum(x))) > 1e-6:
1228
1228
  raise ValueError(
1229
1229
  'The sum of fractions in the state to be discretized must'
myokit/lib/plots.py CHANGED
@@ -62,28 +62,28 @@ def simulation_times(
62
62
  def stair(ax, time, realtime, evaluations):
63
63
  if time is None:
64
64
  raise ValueError('This plotting mode requires "time" to be set.')
65
- time = np.array(time, copy=False)
65
+ time = np.asarray(time)
66
66
  step = np.arange(0, len(time))
67
67
  ax.step(time, step, label=label)
68
68
 
69
69
  def stair_inverse(ax, time, realtime, evaluations):
70
70
  if time is None:
71
71
  raise ValueError('This plotting mode requires "time" to be set.')
72
- time = np.array(time, copy=False)
72
+ time = np.asarray(time)
73
73
  step = np.arange(0, len(time))
74
74
  ax.step(step, time, label=label)
75
75
 
76
76
  def load(ax, time, realtime, evaluations):
77
77
  if time is None:
78
78
  raise ValueError('This plotting mode requires "time" to be set.')
79
- time = np.array(time, copy=False)
79
+ time = np.asarray(time)
80
80
  size = np.log(1 / (time[1:] - time[:-1]))
81
81
  ax.step(time[1:], size, label=label)
82
82
 
83
83
  def histo(ax, time, realtime, evaluations):
84
84
  if time is None:
85
85
  raise ValueError('This plotting mode requires "time" to be set.')
86
- time = np.array(time, copy=False)
86
+ time = np.asarray(time)
87
87
  zero = float(time[0])
88
88
  bucket_w = (time[-1] - zero) / nbuckets
89
89
  bucket_x = np.zeros(nbuckets)
@@ -167,11 +167,11 @@ class DataBlock1dTest(unittest.TestCase):
167
167
  d = myokit.DataLog()
168
168
  d.set_time_key('time')
169
169
  d['time'] = time
170
- d['x', 0] = np.array(down, copy=True)
171
- d['x', 1] = np.array(down, copy=True)
172
- d['x', 2] = np.array(down, copy=True)
173
- d['x', 3] = np.array(down, copy=True)
174
- d['x', 4] = np.array(down, copy=True)
170
+ d['x', 0] = np.copy(down)
171
+ d['x', 1] = np.copy(down)
172
+ d['x', 2] = np.copy(down)
173
+ d['x', 3] = np.copy(down)
174
+ d['x', 4] = np.copy(down)
175
175
  d['x', 1][10:] = 40
176
176
  b = myokit.DataBlock1d.from_log(d)
177
177
  self.assertEqual(b.cv('x'), 0)
@@ -180,11 +180,11 @@ class DataBlock1dTest(unittest.TestCase):
180
180
  d = myokit.DataLog()
181
181
  d.set_time_key('time')
182
182
  d['time'] = time
183
- d['x', 0] = np.array(down, copy=True)
184
- d['x', 1] = np.array(down, copy=True)
185
- d['x', 2] = np.array(down, copy=True)
186
- d['x', 3] = np.array(down, copy=True)
187
- d['x', 4] = np.array(down, copy=True)
183
+ d['x', 0] = np.copy(down)
184
+ d['x', 1] = np.copy(down)
185
+ d['x', 2] = np.copy(down)
186
+ d['x', 3] = np.copy(down)
187
+ d['x', 4] = np.copy(down)
188
188
  d['x', 1][10:] = 40
189
189
  d['x', 2][10:] = 40
190
190
  d['x', 3][10:] = 40
@@ -2109,9 +2109,12 @@ class DataLogTest(unittest.TestCase):
2109
2109
  self.assertRaises(ValueError, d.split_periodic, 0)
2110
2110
  self.assertRaises(ValueError, d.split_periodic, -1)
2111
2111
 
2112
- # Test larger period than log data
2112
+ # Test larger period than log data: Should return length-1 list
2113
2113
  d['x'] = [4, 5, 6, 7]
2114
2114
  e = d.split_periodic(100)
2115
+ self.assertIsInstance(e, list)
2116
+ self.assertEqual(len(e), 1)
2117
+ e = e[0]
2115
2118
  self.assertEqual(set(d.keys()), set(e.keys()))
2116
2119
  self.assertFalse(d is e)
2117
2120