myokit 1.35.0__py3-none-any.whl → 1.35.2__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 +11 -14
  2. myokit/__main__.py +0 -3
  3. myokit/_config.py +1 -3
  4. myokit/_datablock.py +914 -12
  5. myokit/_model_api.py +1 -3
  6. myokit/_myokit_version.py +1 -1
  7. myokit/_protocol.py +14 -28
  8. myokit/_sim/cable.c +1 -1
  9. myokit/_sim/cable.py +3 -2
  10. myokit/_sim/cmodel.h +1 -0
  11. myokit/_sim/cvodessim.c +79 -42
  12. myokit/_sim/cvodessim.py +20 -8
  13. myokit/_sim/fiber_tissue.c +1 -1
  14. myokit/_sim/fiber_tissue.py +3 -2
  15. myokit/_sim/openclsim.c +1 -1
  16. myokit/_sim/openclsim.py +8 -11
  17. myokit/_sim/pacing.h +121 -106
  18. myokit/_unit.py +1 -1
  19. myokit/formats/__init__.py +178 -0
  20. myokit/formats/axon/_abf.py +911 -841
  21. myokit/formats/axon/_atf.py +62 -59
  22. myokit/formats/axon/_importer.py +2 -2
  23. myokit/formats/heka/__init__.py +38 -0
  24. myokit/formats/heka/_importer.py +39 -0
  25. myokit/formats/heka/_patchmaster.py +2512 -0
  26. myokit/formats/wcp/_wcp.py +318 -133
  27. myokit/gui/datablock_viewer.py +144 -77
  28. myokit/gui/datalog_viewer.py +212 -231
  29. myokit/tests/ansic_event_based_pacing.py +3 -3
  30. myokit/tests/{ansic_fixed_form_pacing.py → ansic_time_series_pacing.py} +6 -6
  31. myokit/tests/data/formats/abf-v2.abf +0 -0
  32. myokit/tests/test_datablock.py +84 -0
  33. myokit/tests/test_datalog.py +2 -1
  34. myokit/tests/test_formats_axon.py +589 -136
  35. myokit/tests/test_formats_wcp.py +191 -22
  36. myokit/tests/test_pacing_system_c.py +51 -23
  37. myokit/tests/test_pacing_system_py.py +18 -0
  38. myokit/tests/test_simulation_1d.py +62 -22
  39. myokit/tests/test_simulation_cvodes.py +52 -3
  40. myokit/tests/test_simulation_fiber_tissue.py +35 -4
  41. myokit/tests/test_simulation_opencl.py +28 -4
  42. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/LICENSE.txt +1 -1
  43. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/METADATA +1 -1
  44. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/RECORD +47 -44
  45. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/WHEEL +0 -0
  46. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/entry_points.txt +0 -0
  47. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/top_level.txt +0 -0
myokit/_sim/pacing.h CHANGED
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  * pacing.h
3
3
  *
4
- * Ansi-C implementation for event-based pacing (using a Myokit Protocol
5
- * object) and fixed-form pacing (using a time-series).
4
+ * Ansi-C implementation for event-based pacing (using a myokit.Protocol) and
5
+ * time series pacing (using a myokit.TimeSeriesProtocol).
6
6
  *
7
7
  * How to use event-based pacing:
8
8
  *
@@ -22,13 +22,12 @@
22
22
  * Flags are used to indicate errors. If a flag other than ESys_OK is set, a
23
23
  * call to ESys_SetPyErr(flag) can be made to set a Python exception.
24
24
  *
25
+ * How to use time series pacing:
25
26
  *
26
- * How to use fixed-form pacing:
27
- *
28
- * 1. Create a pacing system using FSys_Create
29
- * 2. Populate it using two Python lists via FSys_Populate
30
- * 3. Obtain the pacing value for any time using FSys_GetLevel
31
- * 4. Tidy up using FSys_Destroy
27
+ * 1. Create a pacing system using TSys_Create
28
+ * 2. Populate it using two Python lists via TSys_Populate
29
+ * 3. Obtain the pacing value for any time using TSys_GetLevel
30
+ * 4. Tidy up using TSys_Destroy
32
31
  *
33
32
  * This file is part of Myokit.
34
33
  * See http://myokit.org for copyright, sharing, and licensing details.
@@ -217,11 +216,12 @@ ESys_ScheduleEvent(ESys_Event head, ESys_Event add, ESys_Flag* flag)
217
216
  * Pacing system
218
217
  */
219
218
  struct ESys_Mem {
220
- Py_ssize_t n_events; // The number of events in this system
221
- double time; // The current time
222
- ESys_Event events; // The events, stored as an array
223
- ESys_Event head; // The head of the event queue
224
- ESys_Event fire; // The currently active event
219
+ Py_ssize_t n_events; // The number of events in this system
220
+ double time; // The current time
221
+ double initial_time; // The initial time (used by reset)
222
+ ESys_Event events; // The events, stored as an array
223
+ ESys_Event head; // The head of the event queue
224
+ ESys_Event fire; // The currently active event
225
225
  double tnext; // The time of the next event start or finish
226
226
  double tdown; // The time the active event is over
227
227
  double level; // The current output value
@@ -237,7 +237,7 @@ typedef struct ESys_Mem* ESys;
237
237
  * Returns the newly created pacing system
238
238
  */
239
239
  ESys
240
- ESys_Create(ESys_Flag* flag)
240
+ ESys_Create(double initial_time, ESys_Flag* flag)
241
241
  {
242
242
  ESys sys = (ESys)malloc(sizeof(struct ESys_Mem));
243
243
  if (sys == 0) {
@@ -245,13 +245,14 @@ ESys_Create(ESys_Flag* flag)
245
245
  return 0;
246
246
  }
247
247
 
248
- sys->time = 0;
248
+ sys->time = initial_time;
249
+ sys->initial_time = initial_time;
249
250
  sys->n_events = -1; // Used to indicate unpopulated system
250
251
  sys->events = NULL;
251
252
  sys->head = NULL;
252
253
  sys->fire = NULL;
253
- sys->tnext = 0;
254
- sys->tdown = 0;
254
+ sys->tnext = initial_time;
255
+ sys->tdown = initial_time;
255
256
  sys->level = 0;
256
257
 
257
258
  if(flag != 0) *flag = ESys_OK;
@@ -315,11 +316,11 @@ ESys_Reset(ESys sys)
315
316
  }
316
317
 
317
318
  // Reset the properties of the event system
318
- sys->time = 0;
319
+ sys->time = sys->initial_time;
319
320
  sys->head = head;
320
321
  sys->fire = 0;
321
- sys->tnext = 0;
322
- sys->tdown = 0;
322
+ sys->tnext = sys->initial_time;
323
+ sys->tdown = sys->initial_time;
323
324
  sys->level = 0;
324
325
 
325
326
  return ESys_OK;
@@ -596,113 +597,113 @@ ESys_GetLevel(ESys sys, ESys_Flag* flag)
596
597
 
597
598
  /*
598
599
  *
599
- * Fixed-form code starts here
600
+ * Time-series code starts here
600
601
  *
601
602
  */
602
603
 
603
604
  /*
604
- * Fixed-form pacing error flags
605
+ * Time series pacing error flags
605
606
  */
606
- typedef int FSys_Flag;
607
- #define FSys_OK 0
608
- #define FSys_OUT_OF_MEMORY -1
607
+ typedef int TSys_Flag;
608
+ #define TSys_OK 0
609
+ #define TSys_OUT_OF_MEMORY -1
609
610
  // General
610
- #define FSys_INVALID_SYSTEM -10
611
- #define FSys_POPULATED_SYSTEM -11
612
- #define FSys_UNPOPULATED_SYSTEM -12
611
+ #define TSys_INVALID_SYSTEM -10
612
+ #define TSys_POPULATED_SYSTEM -11
613
+ #define TSys_UNPOPULATED_SYSTEM -12
613
614
  // Populating the system
614
- #define FSys_POPULATE_INVALID_TIMES -20
615
- #define FSys_POPULATE_INVALID_VALUES -21
616
- #define FSys_POPULATE_SIZE_MISMATCH -22
617
- #define FSys_POPULATE_NOT_ENOUGH_DATA -23
618
- #define FSys_POPULATE_INVALID_TIMES_DATA -24
619
- #define FSys_POPULATE_INVALID_VALUES_DATA -25
620
- #define FSys_POPULATE_DECREASING_TIMES_DATA -26
621
- #define FSys_POPULATE_INVALID_PROTOCOL -27
615
+ #define TSys_POPULATE_INVALID_TIMES -20
616
+ #define TSys_POPULATE_INVALID_VALUES -21
617
+ #define TSys_POPULATE_SIZE_MISMATCH -22
618
+ #define TSys_POPULATE_NOT_ENOUGH_DATA -23
619
+ #define TSys_POPULATE_INVALID_TIMES_DATA -24
620
+ #define TSys_POPULATE_INVALID_VALUES_DATA -25
621
+ #define TSys_POPULATE_DECREASING_TIMES_DATA -26
622
+ #define TSys_POPULATE_INVALID_PROTOCOL -27
622
623
 
623
624
  /*
624
- * Sets a python exception based on a fixed-form pacing error flag.
625
+ * Sets a python exception based on a time-series pacing error flag.
625
626
  *
626
627
  * Arguments
627
628
  * flag : The python error flag to base the message on.
628
629
  */
629
630
  void
630
- FSys_SetPyErr(FSys_Flag flag)
631
+ TSys_SetPyErr(TSys_Flag flag)
631
632
  {
632
633
  switch(flag) {
633
- case FSys_OK:
634
+ case TSys_OK:
634
635
  break;
635
- case FSys_OUT_OF_MEMORY:
636
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Memory allocation failed.");
636
+ case TSys_OUT_OF_MEMORY:
637
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Memory allocation failed.");
637
638
  break;
638
639
  // General
639
- case FSys_INVALID_SYSTEM:
640
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Invalid pacing system provided.");
640
+ case TSys_INVALID_SYSTEM:
641
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid pacing system provided.");
641
642
  break;
642
- case FSys_POPULATED_SYSTEM:
643
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Pacing system already populated.");
643
+ case TSys_POPULATED_SYSTEM:
644
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Pacing system already populated.");
644
645
  break;
645
- case FSys_UNPOPULATED_SYSTEM:
646
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Pacing system not populated.");
646
+ case TSys_UNPOPULATED_SYSTEM:
647
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Pacing system not populated.");
647
648
  break;
648
649
  // Populate
649
- case FSys_POPULATE_INVALID_PROTOCOL:
650
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Invalid protocol python object passed.");
650
+ case TSys_POPULATE_INVALID_PROTOCOL:
651
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid protocol python object passed.");
651
652
  break;
652
- case FSys_POPULATE_INVALID_TIMES:
653
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Invalid times array passed.");
653
+ case TSys_POPULATE_INVALID_TIMES:
654
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid times array passed.");
654
655
  break;
655
- case FSys_POPULATE_INVALID_VALUES:
656
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Invalid values array passed.");
656
+ case TSys_POPULATE_INVALID_VALUES:
657
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid values array passed.");
657
658
  break;
658
- case FSys_POPULATE_SIZE_MISMATCH:
659
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Sizes of times and values arrays don't match.");
659
+ case TSys_POPULATE_SIZE_MISMATCH:
660
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Sizes of times and values arrays don't match.");
660
661
  break;
661
- case FSys_POPULATE_NOT_ENOUGH_DATA:
662
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Time-series must contain at least two data points.");
662
+ case TSys_POPULATE_NOT_ENOUGH_DATA:
663
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Time-series must contain at least two data points.");
663
664
  break;
664
- case FSys_POPULATE_INVALID_TIMES_DATA:
665
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Times array must contain only floats.");
665
+ case TSys_POPULATE_INVALID_TIMES_DATA:
666
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Times array must contain only floats.");
666
667
  break;
667
- case FSys_POPULATE_INVALID_VALUES_DATA:
668
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Values array must contain only floats.");
668
+ case TSys_POPULATE_INVALID_VALUES_DATA:
669
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Values array must contain only floats.");
669
670
  break;
670
- case FSys_POPULATE_DECREASING_TIMES_DATA:
671
- PyErr_SetString(PyExc_Exception, "F-Pacing error: Times array must be non-decreasing.");
671
+ case TSys_POPULATE_DECREASING_TIMES_DATA:
672
+ PyErr_SetString(PyExc_Exception, "T-Pacing error: Times array must be non-decreasing.");
672
673
  break;
673
674
  // Unknown
674
675
  default:
675
- PyErr_Format(PyExc_Exception, "F-Pacing error: Unlisted error %d", (int)flag);
676
+ PyErr_Format(PyExc_Exception, "T-Pacing error: Unlisted error %d", (int)flag);
676
677
  break;
677
678
  };
678
679
  }
679
680
 
680
681
  /*
681
- * Fixed-form pacing system
682
+ * Time series pacing system
682
683
  */
683
- struct FSys_Mem {
684
+ struct TSys_Mem {
684
685
  Py_ssize_t n_points; // The number of entries in the time and pace arrays
685
686
  double* times; // The time array
686
687
  double* values; // The values array
687
688
  Py_ssize_t last_index; // The index of the most recently returned value
688
689
  //double level; // The current output value
689
690
  };
690
- typedef struct FSys_Mem* FSys;
691
+ typedef struct TSys_Mem* TSys;
691
692
 
692
693
  /*
693
- * Creates a fixed-form pacing system
694
+ * Creates a time series pacing system
694
695
  *
695
696
  * Arguments
696
- * flag : The address of a fixed-form pacing error flag or NULL
697
+ * flag : The address of a time series pacing error flag or NULL
697
698
  *
698
- * Returns the newly created fixed-form pacing system
699
+ * Returns the newly created time series pacing system
699
700
  */
700
- FSys
701
- FSys_Create(FSys_Flag* flag)
701
+ TSys
702
+ TSys_Create(TSys_Flag* flag)
702
703
  {
703
- FSys sys = (FSys)malloc(sizeof(struct FSys_Mem));
704
+ TSys sys = (TSys)malloc(sizeof(struct TSys_Mem));
704
705
  if (sys == 0) {
705
- if(flag != 0) *flag = FSys_OUT_OF_MEMORY;
706
+ if(flag != 0) *flag = TSys_OUT_OF_MEMORY;
706
707
  return 0;
707
708
  }
708
709
 
@@ -711,22 +712,22 @@ FSys_Create(FSys_Flag* flag)
711
712
  sys->values = NULL;
712
713
  sys->last_index = 0;
713
714
 
714
- if(flag != 0) *flag = FSys_OK;
715
+ if(flag != 0) *flag = TSys_OK;
715
716
  return sys;
716
717
  }
717
718
 
718
719
  /*
719
- * Destroys a fixed-form pacing system and frees the memory it occupies.
720
+ * Destroys a time series pacing system and frees the memory it occupies.
720
721
  *
721
722
  * Arguments
722
- * sys : The fixed-form pacing system to destroy
723
+ * sys : The time series pacing system to destroy
723
724
  *
724
- * Returns a fixed-form pacing error flag.
725
+ * Returns a time series pacing error flag.
725
726
  */
726
- FSys_Flag
727
- FSys_Destroy(FSys sys)
727
+ TSys_Flag
728
+ TSys_Destroy(TSys sys)
728
729
  {
729
- if(sys == 0) return FSys_INVALID_SYSTEM;
730
+ if(sys == 0) return TSys_INVALID_SYSTEM;
730
731
  if(sys->times != NULL) {
731
732
  free(sys->times);
732
733
  sys->times = NULL;
@@ -736,39 +737,39 @@ FSys_Destroy(FSys sys)
736
737
  sys->values = NULL;
737
738
  }
738
739
  free(sys);
739
- return FSys_OK;
740
+ return TSys_OK;
740
741
  }
741
742
 
742
743
  /*
743
- * Populates a fixed-form pacing system using two Python list objects
744
+ * Populates a time series pacing system using two Python list objects
744
745
  * containing an equal number of floating point numbers.
745
746
  * Returns an error if the system already has data.
746
747
  *
747
748
  * Arguments
748
- * sys : The fixed-form pacing system to add the data to.
749
+ * sys : The time series pacing system to add the data to.
749
750
  * times : A Python list of (non-decreasing) floats.
750
751
  * values : An equally sized Python list of floats.
751
752
  *
752
- * Returns a fixed-form pacing error flag.
753
+ * Returns a time series pacing error flag.
753
754
  */
754
- FSys_Flag
755
- FSys_Populate(FSys sys, PyObject* protocol)
755
+ TSys_Flag
756
+ TSys_Populate(TSys sys, PyObject* protocol)
756
757
  {
757
758
  int i;
758
759
  Py_ssize_t n;
759
760
  PyObject *times_list, *values_list;
760
761
 
761
762
  // Check ESys
762
- if(sys == 0) return FSys_INVALID_SYSTEM;
763
- if (sys->n_points != -1) return FSys_POPULATED_SYSTEM;
764
- if (protocol == Py_None) return FSys_POPULATE_INVALID_PROTOCOL;
763
+ if(sys == 0) return TSys_INVALID_SYSTEM;
764
+ if (sys->n_points != -1) return TSys_POPULATED_SYSTEM;
765
+ if (protocol == Py_None) return TSys_POPULATE_INVALID_PROTOCOL;
765
766
 
766
767
  // Get PyList from protocol (will need to decref!)
767
768
  times_list = PyObject_CallMethod(protocol, "times", NULL); // Returns a new reference
768
- if(times_list == NULL) return FSys_POPULATE_INVALID_PROTOCOL;
769
+ if(times_list == NULL) return TSys_POPULATE_INVALID_PROTOCOL;
769
770
  if(!PyList_Check(times_list)) {
770
771
  Py_DECREF(times_list);
771
- return FSys_POPULATE_INVALID_TIMES;
772
+ return TSys_POPULATE_INVALID_TIMES;
772
773
  }
773
774
 
774
775
  // Check and convert times list
@@ -782,12 +783,12 @@ FSys_Populate(FSys sys, PyObject* protocol)
782
783
 
783
784
  if (PyErr_Occurred()) {
784
785
  free(sys->times); sys->times = NULL;
785
- return FSys_POPULATE_INVALID_TIMES_DATA;
786
+ return TSys_POPULATE_INVALID_TIMES_DATA;
786
787
  }
787
788
  for(i=1; i<n; i++) {
788
789
  if(sys->times[i] < sys->times[i-1]) {
789
790
  free(sys->times); sys->times = NULL;
790
- return FSys_POPULATE_DECREASING_TIMES_DATA;
791
+ return TSys_POPULATE_DECREASING_TIMES_DATA;
791
792
  }
792
793
  }
793
794
 
@@ -795,12 +796,12 @@ FSys_Populate(FSys sys, PyObject* protocol)
795
796
  values_list = PyObject_CallMethod(protocol, (char*)"values", NULL); // Returns a new reference
796
797
  if(values_list == NULL) {
797
798
  free(sys->times); sys->times = NULL;
798
- return FSys_POPULATE_INVALID_PROTOCOL;
799
+ return TSys_POPULATE_INVALID_PROTOCOL;
799
800
  }
800
801
  if(!PyList_Check(values_list) || PyList_Size(values_list) != n) {
801
802
  free(sys->times); sys->times = NULL;
802
803
  Py_DECREF(values_list);
803
- return FSys_POPULATE_INVALID_VALUES;
804
+ return TSys_POPULATE_INVALID_VALUES;
804
805
  }
805
806
  sys->values = (double*)malloc((size_t)n * sizeof(double));
806
807
  for(i=0; i<n; i++) {
@@ -812,13 +813,13 @@ FSys_Populate(FSys sys, PyObject* protocol)
812
813
  if (PyErr_Occurred()) {
813
814
  free(sys->times); sys->times = NULL;
814
815
  free(sys->values); sys->values = NULL;
815
- return FSys_POPULATE_INVALID_VALUES_DATA;
816
+ return TSys_POPULATE_INVALID_VALUES_DATA;
816
817
  }
817
818
 
818
819
  // Update pacing system and return
819
820
  sys->n_points = n;
820
821
  sys->last_index = 0;
821
- return FSys_OK;
822
+ return TSys_OK;
822
823
  }
823
824
 
824
825
  /*
@@ -834,7 +835,7 @@ FSys_Populate(FSys sys, PyObject* protocol)
834
835
  * using the flag argument!
835
836
  */
836
837
  double
837
- FSys_GetLevel(FSys sys, double time, FSys_Flag* flag)
838
+ TSys_GetLevel(TSys sys, double time, TSys_Flag* flag)
838
839
  {
839
840
  // Index and time at left, mid and right point, plus guessed point
840
841
  Py_ssize_t ileft, imid, iright, iguess;
@@ -843,11 +844,11 @@ FSys_GetLevel(FSys sys, double time, FSys_Flag* flag)
843
844
 
844
845
  // Check system
845
846
  if(sys == 0) {
846
- if(flag != 0) *flag = FSys_INVALID_SYSTEM;
847
+ if(flag != 0) *flag = TSys_INVALID_SYSTEM;
847
848
  return -1;
848
849
  }
849
850
  if(sys->n_points < 0) {
850
- if(flag != 0) *flag = FSys_UNPOPULATED_SYSTEM;
851
+ if(flag != 0) *flag = TSys_UNPOPULATED_SYSTEM;
851
852
  return -1;
852
853
  }
853
854
 
@@ -860,7 +861,7 @@ FSys_GetLevel(FSys sys, double time, FSys_Flag* flag)
860
861
  tleft = sys->times[ileft];
861
862
  if (tleft > time) {
862
863
  // Out-of-bounds on the left, return left-most value
863
- if(flag != 0) *flag = FSys_OK;
864
+ if(flag != 0) *flag = TSys_OK;
864
865
  return sys->values[ileft];
865
866
  }
866
867
 
@@ -869,7 +870,7 @@ FSys_GetLevel(FSys sys, double time, FSys_Flag* flag)
869
870
  tright = sys->times[iright];
870
871
  if (tright <= time) {
871
872
  // Out-of-bounds on the right, return right-most value
872
- if(flag != 0) *flag = FSys_OK;
873
+ if(flag != 0) *flag = TSys_OK;
873
874
  return sys->values[iright];
874
875
  }
875
876
 
@@ -909,18 +910,32 @@ FSys_GetLevel(FSys sys, double time, FSys_Flag* flag)
909
910
 
910
911
  // Handle special case of time == tright
911
912
  // (Because otherwise it can happen that tleft == tright, which would give
912
- // a divide-by-zero in the interpolateion)
913
+ // a divide-by-zero in the interpolation)
913
914
  if (time == tright) {
914
- if(flag != 0) *flag = FSys_OK;
915
+ if(flag != 0) *flag = TSys_OK;
915
916
  sys->last_index = iright;
916
917
  return sys->values[iright];
917
918
  }
918
919
 
919
920
  // Find the correct value using linear interpolation
920
- if(flag != 0) *flag = FSys_OK;
921
+ if(flag != 0) *flag = TSys_OK;
921
922
  sys->last_index = ileft;
922
923
  vleft = sys->values[ileft];
923
924
  return vleft + (sys->values[iright] - vleft) * (time - tleft) / (tright - tleft);
924
925
  }
925
926
 
927
+ /*
928
+ * Pacing types
929
+ */
930
+ union PSys {
931
+ ESys esys;
932
+ TSys tsys;
933
+ };
934
+
935
+ enum PSysType {
936
+ PSys_NOT_SET,
937
+ ESys_TYPE,
938
+ TSys_TYPE
939
+ };
940
+
926
941
  #endif
myokit/_unit.py CHANGED
@@ -258,7 +258,7 @@ class Unit:
258
258
 
259
259
  Returns a :class:`myokit.Quantity`.
260
260
 
261
- Raises a :class:`myokit.IncompatibleUnitError` if the units cannot be
261
+ Raises an :class:`myokit.IncompatibleUnitError` if the units cannot be
262
262
  converted.'
263
263
  """
264
264
  # Check unit1
@@ -664,3 +664,181 @@ def register_external_ewriter(name, ewriter_class):
664
664
  _scan_for_internal_formats()
665
665
  _EWRITERS[name] = ewriter_class
666
666
 
667
+
668
+ class SweepSource:
669
+ """
670
+ Interface for classes that provide time-series data organised into *sweeps*
671
+ (or *records* or *episodes*) and *channels* (or *traces*).
672
+
673
+ The :class:`SweepSource` interface defines methods to get the number of
674
+ sweeps and channels, the names and units of the channels, and the data
675
+ stored in channels either as numpy arrays or in a :class:`myokit.DataLog`.
676
+
677
+ Each sweep contains the same number of channels, and each channel is
678
+ represented as a 1d array. In most cases these arrays have the same length
679
+ for every sweep, but whether this is the case for the current source can be
680
+ tested with :meth:`equal_length_sweeps`.
681
+
682
+ Data can be retrieved sweep by sweep::
683
+
684
+ (time[0], sweep[0]), n_t data points, n_c channels of n_t points
685
+ (time[1], sweep[1]),
686
+ (time[2], sweep[2]),
687
+ ...
688
+ (time[n_s], sweep[n_s])
689
+
690
+ This allows plotting in the common "overlaid" fashion, i.e. plotting every
691
+ ``sweep[i] against ``time[0]``.
692
+
693
+ For other types of analysis (e.g. parameter estimation) the data can also
694
+ be returned as single time series::
695
+
696
+ time sum(n_t[i]) data points
697
+ sweep[0] + sweep[1] + ... n_c channels, with sum(n_t[i]) points
698
+
699
+ Some formats can also contain information from which D/A output signals can
700
+ be reconstructed. These can be accessed using the :meth:`da`.
701
+
702
+ """
703
+ def channel(self, channel_id, join_sweeps=False):
704
+ """
705
+ Returns the data for a single channel, identified by integer or string
706
+ ``channel_id``.
707
+
708
+ With ``join_sweeps=False``, the data is returned as a tuple
709
+ ``(times, sweeps)`` where ``times`` and ``sweeps`` are 2d numpy arrays
710
+ indexed so that ``times[i][j]`` is the ``j``-th time point for sweep
711
+ ``i``.
712
+
713
+ If ``join_sweeps=True`` the sweeps are joined together, and a tuple
714
+ ``(times, values)`` is returned ``times`` and ``values`` are 1d arrays.
715
+ """
716
+ raise NotImplementedError
717
+
718
+ def channel_count(self):
719
+ """ Returns the number of channels. """
720
+ raise NotImplementedError
721
+
722
+ def channel_names(self, index=None):
723
+ """
724
+ Returns the names of all channels or the name of a specific channel
725
+ ``index``.
726
+ """
727
+ raise NotImplementedError
728
+
729
+ def channel_units(self, index=None):
730
+ """
731
+ Returns the units (as :class:`myokit.Unit`) of all channels or the
732
+ units of a specific channel ``index``.
733
+ """
734
+ raise NotImplementedError
735
+
736
+ def da(self, output_id, join_sweeps=False):
737
+ """ Like :meth:`channel`, but returns reconstructed D/A signals. """
738
+ raise NotImplementedError
739
+
740
+ def da_count(self):
741
+ """
742
+ Returns the available number of reconstructed D/A output channels.
743
+
744
+ This should return 0 if D/A channels are not supported.
745
+ """
746
+ raise NotImplementedError
747
+
748
+ def da_names(self, index=None):
749
+ """
750
+ Returns the names of all reconstructed D/A output channels or the name
751
+ of a specific output channel ``index``.
752
+
753
+ This will raise a ``NotImplementedError`` if D/A channels are not
754
+ supported.
755
+ """
756
+ raise NotImplementedError
757
+
758
+ def da_protocol(self, output_id=None, tu='ms', vu='mV', cu='pA',
759
+ n_digits=9):
760
+ """
761
+ Returns a :class:`myokit.Protocol` representation of the D/A output
762
+ channel specified by name or integer ``output_id``.
763
+
764
+ If no explicit output channel is set, a guess will be made.
765
+
766
+ Time units will be converted to ``tu``, voltage units to ``vu``, and
767
+ current units to ``cu``. Other unit types will be left unchanged. Units
768
+ may be specified as :class:`myokit.Unit` objects or strings.
769
+
770
+ By default, floating point numbers in the protocol will be rounded to
771
+ 9 digits after the decimal point. This can be customised using the
772
+ argument ``n_digits``.
773
+
774
+ If a D/A output cannot be converted to a :class:`myokit.Protocol`, a
775
+ ``ValueError`` is raised. A ``NotImplementedError`` is raised if D/A
776
+ channels are not supported at all.
777
+ """
778
+ raise NotImplementedError
779
+
780
+ def da_units(self, index=None):
781
+ """
782
+ Returns the units (as :class:`myokit.Unit`) of all reconstructed D/A
783
+ output channels or the units of a specific output channel ``index``.
784
+
785
+ This will raise a ``NotImplementedError`` if D/A channels are not
786
+ supported.
787
+ """
788
+ raise NotImplementedError
789
+
790
+ def equal_length_sweeps(self):
791
+ """
792
+ Returns ``True`` only if each sweep in this source has the same length.
793
+ """
794
+ raise NotImplementedError
795
+
796
+ def log(self, join_sweeps=False, use_names=False, include_da=True):
797
+ """
798
+ Returns a :class:`myokit.DataLog` containing the data from all
799
+ channels.
800
+
801
+ The log will have a single entry ``time`` corresponding to the time of
802
+ the first sweep if ``join_sweeps`` is ``False``, or the time of all
803
+ points when ``join_sweeps`` is ``True``.
804
+
805
+ Names will have a systematic form ``i_sweep.i_channel.label``, for
806
+ example ``0.1.channel`` for sweep 0 of recorded channel 1, or
807
+ ``3.0.da`` for sweep 3 of reconstructed D/A output 0. These can also be
808
+ accessed using the syntax ``log['channel', 1, 0]`` and
809
+ ``log['da', 0, 3]``.
810
+
811
+ To obtain a log with the user-specified names from the source instead,
812
+ set ``use_names`` to ``True``. This will result in names such as
813
+ ``0.IN 1`` or ``3.Cmd 0``.
814
+
815
+ To exclude D/A signal reconstructions, set ``include_da`` to ``False``.
816
+
817
+ A call with ``join_sweeps=False`` on a source where
818
+ :meth:`equal_length_sweeps()` returns ``False`` will raise a
819
+ ``ValueError``.
820
+ """
821
+ raise NotImplementedError
822
+
823
+ def meta_str(self, verbose=False):
824
+ """
825
+ Optional method that returns a multi-line string with unstructured meta
826
+ data about the source and its contents.
827
+
828
+ Will return ``None`` if no such string is available.
829
+ """
830
+ return None # pragma: no cover
831
+
832
+ def sweep_count(self):
833
+ """
834
+ Returns the number of sweeps.
835
+
836
+ Note that a source with zero recorded channels may still report a
837
+ non-zero number of sweeps if it can provide D/A outputs.
838
+ """
839
+ raise NotImplementedError
840
+
841
+ def time_unit(self):
842
+ """ Returns the time unit used in this source. """
843
+ raise NotImplementedError
844
+