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.
- myokit/__init__.py +11 -14
- myokit/__main__.py +0 -3
- myokit/_config.py +1 -3
- myokit/_datablock.py +914 -12
- myokit/_model_api.py +1 -3
- myokit/_myokit_version.py +1 -1
- myokit/_protocol.py +14 -28
- myokit/_sim/cable.c +1 -1
- myokit/_sim/cable.py +3 -2
- myokit/_sim/cmodel.h +1 -0
- myokit/_sim/cvodessim.c +79 -42
- myokit/_sim/cvodessim.py +20 -8
- myokit/_sim/fiber_tissue.c +1 -1
- myokit/_sim/fiber_tissue.py +3 -2
- myokit/_sim/openclsim.c +1 -1
- myokit/_sim/openclsim.py +8 -11
- myokit/_sim/pacing.h +121 -106
- myokit/_unit.py +1 -1
- myokit/formats/__init__.py +178 -0
- myokit/formats/axon/_abf.py +911 -841
- myokit/formats/axon/_atf.py +62 -59
- myokit/formats/axon/_importer.py +2 -2
- myokit/formats/heka/__init__.py +38 -0
- myokit/formats/heka/_importer.py +39 -0
- myokit/formats/heka/_patchmaster.py +2512 -0
- myokit/formats/wcp/_wcp.py +318 -133
- myokit/gui/datablock_viewer.py +144 -77
- myokit/gui/datalog_viewer.py +212 -231
- myokit/tests/ansic_event_based_pacing.py +3 -3
- myokit/tests/{ansic_fixed_form_pacing.py → ansic_time_series_pacing.py} +6 -6
- myokit/tests/data/formats/abf-v2.abf +0 -0
- myokit/tests/test_datablock.py +84 -0
- myokit/tests/test_datalog.py +2 -1
- myokit/tests/test_formats_axon.py +589 -136
- myokit/tests/test_formats_wcp.py +191 -22
- myokit/tests/test_pacing_system_c.py +51 -23
- myokit/tests/test_pacing_system_py.py +18 -0
- myokit/tests/test_simulation_1d.py +62 -22
- myokit/tests/test_simulation_cvodes.py +52 -3
- myokit/tests/test_simulation_fiber_tissue.py +35 -4
- myokit/tests/test_simulation_opencl.py +28 -4
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/LICENSE.txt +1 -1
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/METADATA +1 -1
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/RECORD +47 -44
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/WHEEL +0 -0
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/entry_points.txt +0 -0
- {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
|
|
5
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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;
|
|
221
|
-
double time;
|
|
222
|
-
|
|
223
|
-
ESys_Event
|
|
224
|
-
ESys_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 =
|
|
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 =
|
|
254
|
-
sys->tdown =
|
|
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 =
|
|
319
|
+
sys->time = sys->initial_time;
|
|
319
320
|
sys->head = head;
|
|
320
321
|
sys->fire = 0;
|
|
321
|
-
sys->tnext =
|
|
322
|
-
sys->tdown =
|
|
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
|
-
*
|
|
600
|
+
* Time-series code starts here
|
|
600
601
|
*
|
|
601
602
|
*/
|
|
602
603
|
|
|
603
604
|
/*
|
|
604
|
-
*
|
|
605
|
+
* Time series pacing error flags
|
|
605
606
|
*/
|
|
606
|
-
typedef int
|
|
607
|
-
#define
|
|
608
|
-
#define
|
|
607
|
+
typedef int TSys_Flag;
|
|
608
|
+
#define TSys_OK 0
|
|
609
|
+
#define TSys_OUT_OF_MEMORY -1
|
|
609
610
|
// General
|
|
610
|
-
#define
|
|
611
|
-
#define
|
|
612
|
-
#define
|
|
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
|
|
615
|
-
#define
|
|
616
|
-
#define
|
|
617
|
-
#define
|
|
618
|
-
#define
|
|
619
|
-
#define
|
|
620
|
-
#define
|
|
621
|
-
#define
|
|
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
|
|
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
|
-
|
|
631
|
+
TSys_SetPyErr(TSys_Flag flag)
|
|
631
632
|
{
|
|
632
633
|
switch(flag) {
|
|
633
|
-
case
|
|
634
|
+
case TSys_OK:
|
|
634
635
|
break;
|
|
635
|
-
case
|
|
636
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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
|
|
640
|
-
PyErr_SetString(PyExc_Exception, "
|
|
640
|
+
case TSys_INVALID_SYSTEM:
|
|
641
|
+
PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid pacing system provided.");
|
|
641
642
|
break;
|
|
642
|
-
case
|
|
643
|
-
PyErr_SetString(PyExc_Exception, "
|
|
643
|
+
case TSys_POPULATED_SYSTEM:
|
|
644
|
+
PyErr_SetString(PyExc_Exception, "T-Pacing error: Pacing system already populated.");
|
|
644
645
|
break;
|
|
645
|
-
case
|
|
646
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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
|
|
650
|
-
PyErr_SetString(PyExc_Exception, "
|
|
650
|
+
case TSys_POPULATE_INVALID_PROTOCOL:
|
|
651
|
+
PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid protocol python object passed.");
|
|
651
652
|
break;
|
|
652
|
-
case
|
|
653
|
-
PyErr_SetString(PyExc_Exception, "
|
|
653
|
+
case TSys_POPULATE_INVALID_TIMES:
|
|
654
|
+
PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid times array passed.");
|
|
654
655
|
break;
|
|
655
|
-
case
|
|
656
|
-
PyErr_SetString(PyExc_Exception, "
|
|
656
|
+
case TSys_POPULATE_INVALID_VALUES:
|
|
657
|
+
PyErr_SetString(PyExc_Exception, "T-Pacing error: Invalid values array passed.");
|
|
657
658
|
break;
|
|
658
|
-
case
|
|
659
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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
|
|
662
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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
|
|
665
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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
|
|
668
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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
|
|
671
|
-
PyErr_SetString(PyExc_Exception, "
|
|
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, "
|
|
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
|
-
*
|
|
682
|
+
* Time series pacing system
|
|
682
683
|
*/
|
|
683
|
-
struct
|
|
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
|
|
691
|
+
typedef struct TSys_Mem* TSys;
|
|
691
692
|
|
|
692
693
|
/*
|
|
693
|
-
* Creates a
|
|
694
|
+
* Creates a time series pacing system
|
|
694
695
|
*
|
|
695
696
|
* Arguments
|
|
696
|
-
* flag : The address of a
|
|
697
|
+
* flag : The address of a time series pacing error flag or NULL
|
|
697
698
|
*
|
|
698
|
-
* Returns the newly created
|
|
699
|
+
* Returns the newly created time series pacing system
|
|
699
700
|
*/
|
|
700
|
-
|
|
701
|
-
|
|
701
|
+
TSys
|
|
702
|
+
TSys_Create(TSys_Flag* flag)
|
|
702
703
|
{
|
|
703
|
-
|
|
704
|
+
TSys sys = (TSys)malloc(sizeof(struct TSys_Mem));
|
|
704
705
|
if (sys == 0) {
|
|
705
|
-
if(flag != 0) *flag =
|
|
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 =
|
|
715
|
+
if(flag != 0) *flag = TSys_OK;
|
|
715
716
|
return sys;
|
|
716
717
|
}
|
|
717
718
|
|
|
718
719
|
/*
|
|
719
|
-
* Destroys a
|
|
720
|
+
* Destroys a time series pacing system and frees the memory it occupies.
|
|
720
721
|
*
|
|
721
722
|
* Arguments
|
|
722
|
-
* sys : The
|
|
723
|
+
* sys : The time series pacing system to destroy
|
|
723
724
|
*
|
|
724
|
-
* Returns a
|
|
725
|
+
* Returns a time series pacing error flag.
|
|
725
726
|
*/
|
|
726
|
-
|
|
727
|
-
|
|
727
|
+
TSys_Flag
|
|
728
|
+
TSys_Destroy(TSys sys)
|
|
728
729
|
{
|
|
729
|
-
if(sys == 0) return
|
|
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
|
|
740
|
+
return TSys_OK;
|
|
740
741
|
}
|
|
741
742
|
|
|
742
743
|
/*
|
|
743
|
-
* Populates a
|
|
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
|
|
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
|
|
753
|
+
* Returns a time series pacing error flag.
|
|
753
754
|
*/
|
|
754
|
-
|
|
755
|
-
|
|
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
|
|
763
|
-
if (sys->n_points != -1) return
|
|
764
|
-
if (protocol == Py_None) return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
913
|
+
// a divide-by-zero in the interpolation)
|
|
913
914
|
if (time == tright) {
|
|
914
|
-
if(flag != 0) *flag =
|
|
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 =
|
|
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
myokit/formats/__init__.py
CHANGED
|
@@ -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
|
+
|