myokit 1.33.9__py3-none-any.whl → 1.35.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 (229) hide show
  1. myokit/__init__.py +9 -36
  2. myokit/__main__.py +76 -142
  3. myokit/_aux.py +62 -16
  4. myokit/_bin/example.mmt +1 -2
  5. myokit/_bin/install-win/menu.json +7 -7
  6. myokit/_config.py +22 -31
  7. myokit/_datablock.py +30 -74
  8. myokit/_datalog.py +49 -72
  9. myokit/_err.py +25 -24
  10. myokit/_expressions.py +50 -68
  11. myokit/_io.py +15 -27
  12. myokit/_model_api.py +453 -249
  13. myokit/_myokit_version.py +1 -5
  14. myokit/_parsing.py +38 -44
  15. myokit/_progress.py +5 -8
  16. myokit/_protocol.py +99 -9
  17. myokit/_sim/__init__.py +7 -24
  18. myokit/_sim/cable.c +6 -8
  19. myokit/_sim/cable.py +6 -8
  20. myokit/_sim/cmodel.h +125 -70
  21. myokit/_sim/cmodel.py +12 -14
  22. myokit/_sim/compiler.py +1 -4
  23. myokit/_sim/cvodessim.c +196 -118
  24. myokit/_sim/cvodessim.py +130 -103
  25. myokit/_sim/differential.hpp +4 -4
  26. myokit/_sim/fiber_tissue.c +4 -8
  27. myokit/_sim/fiber_tissue.py +11 -13
  28. myokit/_sim/jacobian.cpp +2 -2
  29. myokit/_sim/jacobian.py +11 -8
  30. myokit/_sim/mcl.h +53 -55
  31. myokit/_sim/opencl.py +21 -27
  32. myokit/_sim/openclsim.c +3 -7
  33. myokit/_sim/openclsim.cl +3 -3
  34. myokit/_sim/openclsim.py +49 -40
  35. myokit/_sim/pacing.h +36 -16
  36. myokit/_sim/rhs.c +6 -13
  37. myokit/_sim/rhs.py +5 -14
  38. myokit/_sim/sundials.py +1 -4
  39. myokit/_system.py +10 -16
  40. myokit/_unit.py +4 -13
  41. myokit/float.py +0 -3
  42. myokit/formats/__init__.py +8 -10
  43. myokit/formats/ansic/__init__.py +0 -3
  44. myokit/formats/ansic/_ewriter.py +2 -4
  45. myokit/formats/ansic/_exporter.py +1 -4
  46. myokit/formats/ansic/template/cable.c +4 -4
  47. myokit/formats/ansic/template/euler.c +5 -5
  48. myokit/formats/ansic/template/sim.c +6 -6
  49. myokit/formats/axon/__init__.py +1 -3
  50. myokit/formats/axon/_abf.py +12 -17
  51. myokit/formats/axon/_atf.py +5 -6
  52. myokit/formats/axon/_importer.py +0 -3
  53. myokit/formats/cellml/__init__.py +0 -3
  54. myokit/formats/cellml/_ewriter.py +3 -6
  55. myokit/formats/cellml/_exporter.py +3 -6
  56. myokit/formats/cellml/_importer.py +1 -4
  57. myokit/formats/cellml/v1/__init__.py +0 -4
  58. myokit/formats/cellml/v1/_api.py +8 -11
  59. myokit/formats/cellml/v1/_parser.py +2 -5
  60. myokit/formats/cellml/v1/_writer.py +2 -11
  61. myokit/formats/cellml/v2/__init__.py +0 -3
  62. myokit/formats/cellml/v2/_api.py +8 -17
  63. myokit/formats/cellml/v2/_parser.py +2 -5
  64. myokit/formats/cellml/v2/_writer.py +1 -4
  65. myokit/formats/channelml/__init__.py +0 -3
  66. myokit/formats/channelml/_importer.py +11 -21
  67. myokit/formats/cpp/__init__.py +1 -3
  68. myokit/formats/cpp/_ewriter.py +0 -3
  69. myokit/formats/cuda/__init__.py +0 -3
  70. myokit/formats/cuda/_ewriter.py +2 -4
  71. myokit/formats/cuda/_exporter.py +0 -3
  72. myokit/formats/cuda/template/kernel.cu +8 -5
  73. myokit/formats/easyml/__init__.py +0 -3
  74. myokit/formats/easyml/_ewriter.py +9 -11
  75. myokit/formats/easyml/_exporter.py +2 -5
  76. myokit/formats/html/__init__.py +0 -3
  77. myokit/formats/html/_exporter.py +0 -3
  78. myokit/formats/html/_flatten.py +5 -21
  79. myokit/formats/latex/__init__.py +0 -3
  80. myokit/formats/latex/_ewriter.py +1 -4
  81. myokit/formats/latex/_exporter.py +4 -6
  82. myokit/formats/mathml/__init__.py +0 -3
  83. myokit/formats/mathml/_ewriter.py +2 -11
  84. myokit/formats/mathml/_parser.py +4 -6
  85. myokit/formats/matlab/__init__.py +0 -3
  86. myokit/formats/matlab/_ewriter.py +1 -4
  87. myokit/formats/matlab/_exporter.py +2 -5
  88. myokit/formats/matlab/template/main.m +3 -2
  89. myokit/formats/opencl/__init__.py +0 -3
  90. myokit/formats/opencl/_ewriter.py +2 -4
  91. myokit/formats/opencl/_exporter.py +2 -5
  92. myokit/formats/opencl/template/cable.c +10 -10
  93. myokit/formats/opencl/template/kernel.cl +1 -1
  94. myokit/formats/opencl/template/minilog.py +1 -1
  95. myokit/formats/python/__init__.py +0 -3
  96. myokit/formats/python/_ewriter.py +2 -5
  97. myokit/formats/python/_exporter.py +0 -3
  98. myokit/formats/python/template/sim.py +14 -14
  99. myokit/formats/sbml/__init__.py +0 -3
  100. myokit/formats/sbml/_api.py +50 -44
  101. myokit/formats/sbml/_importer.py +1 -4
  102. myokit/formats/sbml/_parser.py +2 -5
  103. myokit/formats/stan/__init__.py +0 -3
  104. myokit/formats/stan/_ewriter.py +2 -4
  105. myokit/formats/stan/_exporter.py +2 -5
  106. myokit/formats/stan/template/cell.stan +3 -3
  107. myokit/formats/sympy/__init__.py +0 -3
  108. myokit/formats/sympy/_ereader.py +1 -4
  109. myokit/formats/sympy/_ewriter.py +2 -5
  110. myokit/formats/wcp/__init__.py +0 -3
  111. myokit/formats/wcp/_wcp.py +2 -8
  112. myokit/formats/xml/__init__.py +0 -3
  113. myokit/formats/xml/_exporter.py +0 -3
  114. myokit/formats/xml/_split.py +0 -3
  115. myokit/gui/__init__.py +80 -246
  116. myokit/gui/datablock_viewer.py +103 -86
  117. myokit/gui/datalog_viewer.py +214 -66
  118. myokit/gui/explorer.py +15 -21
  119. myokit/gui/ide.py +171 -144
  120. myokit/gui/progress.py +9 -9
  121. myokit/gui/source.py +406 -375
  122. myokit/gui/vargrapher.py +2 -12
  123. myokit/lib/deps.py +12 -13
  124. myokit/lib/guess.py +3 -4
  125. myokit/lib/hh.py +20 -18
  126. myokit/lib/markov.py +21 -20
  127. myokit/lib/multi.py +1 -3
  128. myokit/lib/plots.py +20 -9
  129. myokit/pacing.py +0 -3
  130. myokit/pype.py +7 -18
  131. myokit/tests/__init__.py +3 -6
  132. myokit/tests/ansic_event_based_pacing.py +1 -4
  133. myokit/tests/ansic_fixed_form_pacing.py +3 -6
  134. myokit/tests/data/beeler-1977-model-compare-b.mmt +2 -2
  135. myokit/tests/data/clancy-1999-fitting.mmt +1 -0
  136. myokit/tests/test_aux.py +13 -28
  137. myokit/tests/test_cellml_v1_api.py +4 -19
  138. myokit/tests/test_cellml_v1_parser.py +0 -15
  139. myokit/tests/test_cellml_v1_writer.py +0 -9
  140. myokit/tests/test_cellml_v2_api.py +4 -19
  141. myokit/tests/test_cellml_v2_parser.py +0 -15
  142. myokit/tests/test_cellml_v2_writer.py +0 -9
  143. myokit/tests/test_cmodel.py +16 -22
  144. myokit/tests/test_compiler_detection.py +1 -11
  145. myokit/tests/test_component.py +108 -56
  146. myokit/tests/test_config.py +34 -67
  147. myokit/tests/test_datablock.py +1 -9
  148. myokit/tests/test_datalog.py +19 -24
  149. myokit/tests/test_dependency_checking.py +8 -23
  150. myokit/tests/test_expressions.py +0 -9
  151. myokit/tests/test_float.py +1 -5
  152. myokit/tests/test_formats.py +0 -9
  153. myokit/tests/test_formats_axon.py +1 -9
  154. myokit/tests/test_formats_cellml.py +0 -15
  155. myokit/tests/test_formats_channelml.py +0 -15
  156. myokit/tests/test_formats_easyml.py +0 -14
  157. myokit/tests/test_formats_exporters.py +1 -16
  158. myokit/tests/test_formats_expression_writers.py +1 -17
  159. myokit/tests/test_formats_html.py +0 -3
  160. myokit/tests/test_formats_importers.py +1 -16
  161. myokit/tests/test_formats_mathml_content.py +0 -9
  162. myokit/tests/test_formats_mathml_presentation.py +0 -9
  163. myokit/tests/test_formats_opencl.py +0 -10
  164. myokit/tests/test_formats_sbml.py +0 -15
  165. myokit/tests/test_formats_sympy.py +0 -9
  166. myokit/tests/test_formats_wcp.py +1 -3
  167. myokit/tests/test_io.py +27 -27
  168. myokit/tests/test_jacobian_calculator.py +6 -14
  169. myokit/tests/test_jacobian_tracer.py +0 -9
  170. myokit/tests/test_lib_deps.py +0 -9
  171. myokit/tests/test_lib_guess.py +0 -9
  172. myokit/tests/test_lib_hh.py +18 -12
  173. myokit/tests/test_lib_markov.py +21 -13
  174. myokit/tests/test_lib_multi.py +0 -9
  175. myokit/tests/test_lib_plots.py +13 -8
  176. myokit/tests/test_meta.py +0 -3
  177. myokit/tests/test_model.py +390 -96
  178. myokit/tests/test_model_building.py +44 -96
  179. myokit/tests/test_opencl_info.py +5 -14
  180. myokit/tests/test_pacing_factory.py +0 -3
  181. myokit/tests/test_pacing_system_c.py +1 -23
  182. myokit/tests/test_pacing_system_py.py +0 -9
  183. myokit/tests/test_parsing.py +139 -56
  184. myokit/tests/test_progress_reporters.py +0 -3
  185. myokit/tests/test_protocol.py +0 -9
  186. myokit/tests/test_protocol_floating_point.py +1 -10
  187. myokit/tests/test_protocol_time_series.py +82 -0
  188. myokit/tests/test_pype.py +0 -9
  189. myokit/tests/test_quantity.py +0 -9
  190. myokit/tests/test_rhs_benchmarker.py +1 -9
  191. myokit/tests/test_sbml_api.py +27 -42
  192. myokit/tests/test_sbml_parser.py +4 -19
  193. myokit/tests/test_simulation_1d.py +45 -25
  194. myokit/tests/test_simulation_cvodes.py +321 -55
  195. myokit/tests/test_simulation_cvodes_from_disk.py +0 -3
  196. myokit/tests/test_simulation_fiber_tissue.py +39 -12
  197. myokit/tests/test_simulation_log_interval.py +1 -431
  198. myokit/tests/test_simulation_opencl.py +69 -48
  199. myokit/tests/test_simulation_opencl_log_interval.py +1 -3
  200. myokit/tests/test_simulation_opencl_vs_cvode.py +1 -10
  201. myokit/tests/test_simulation_opencl_vs_sim1d.py +1 -10
  202. myokit/tests/test_system_info.py +1 -11
  203. myokit/tests/test_tools.py +0 -9
  204. myokit/tests/test_unit.py +1 -10
  205. myokit/tests/test_user_functions.py +0 -10
  206. myokit/tests/test_variable.py +231 -27
  207. myokit/tools.py +5 -21
  208. myokit/units.py +5 -3
  209. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/METADATA +12 -15
  210. myokit-1.35.0.dist-info/RECORD +391 -0
  211. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/WHEEL +1 -1
  212. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/entry_points.txt +0 -1
  213. myokit/_exec_new.py +0 -15
  214. myokit/_exec_old.py +0 -15
  215. myokit/_sim/cvodesim.c +0 -1551
  216. myokit/_sim/cvodesim.py +0 -674
  217. myokit/_sim/icsim.cpp +0 -563
  218. myokit/_sim/icsim.py +0 -363
  219. myokit/_sim/psim.cpp +0 -656
  220. myokit/_sim/psim.py +0 -493
  221. myokit/lib/common.py +0 -1094
  222. myokit/tests/test_lib_common.py +0 -130
  223. myokit/tests/test_simulation_cvode.py +0 -612
  224. myokit/tests/test_simulation_ic.py +0 -108
  225. myokit/tests/test_simulation_p.py +0 -223
  226. myokit-1.33.9.dist-info/RECORD +0 -403
  227. /myokit/formats/opencl/template/{test → test.sh} +0 -0
  228. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/LICENSE.txt +0 -0
  229. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/top_level.txt +0 -0
myokit/_sim/cvodessim.c CHANGED
@@ -173,11 +173,8 @@ void
173
173
  ErrorHandler(int error_code, const char *module, const char *function,
174
174
  char *msg, void *eh_data)
175
175
  {
176
- char errstr[1024];
177
176
  if (error_code > 0) {
178
- sprintf(errstr, "CVODES: %s", msg);
179
- PyErr_WarnEx(PyExc_RuntimeWarning, errstr, 1);
180
- /* Python 3.2+: PyErr_WarnFormat(PyExc_RuntimeWarning, 1, "CVODES: %s", msg); */
177
+ PyErr_WarnFormat(PyExc_RuntimeWarning, 1, "CVODES: %s", msg);
181
178
  }
182
179
  }
183
180
 
@@ -185,7 +182,7 @@ ErrorHandler(int error_code, const char *module, const char *function,
185
182
  * Initialisation status.
186
183
  * Proper sequence is init(), repeated step() calls till finished, then clean.
187
184
  */
188
- int initialised = 0; /* Has the simulation been initialised */
185
+ int initialized = 0; /* Has the simulation been initialized */
189
186
 
190
187
  /*
191
188
  * Model
@@ -195,11 +192,19 @@ Model model; /* A model object */
195
192
  /*
196
193
  * Pacing
197
194
  */
198
- ESys epacing; /* Event-based pacing system */
199
- PyObject* eprotocol; /* An event-based pacing protocol */
200
- FSys fpacing; /* Fixed-form pacing system */
201
- PyObject* fprotocol; /* A fixed-form pacing protocol */
202
- double pace = 0; /* Pacing value */
195
+ union PSys {
196
+ ESys event;
197
+ FSys fixed;
198
+ };
199
+ enum PSysType {
200
+ EVENT,
201
+ FIXED
202
+ };
203
+ union PSys *pacing_systems; /* Array of pacing system (event or fixed) */
204
+ enum PSysType *pacing_types; /* Array of pacing system types */
205
+ PyObject *protocols; /* The protocols used to generate the pacing systems */
206
+ double* pacing; /* Pacing values, same size as pacing_systems and pacing_types */
207
+ int n_pace; /* The number of pacing systems */
203
208
 
204
209
  /*
205
210
  * CVODE Memory
@@ -289,7 +294,7 @@ PyObject* log_times; /* The point list (or None if disabled) */
289
294
  /*
290
295
  * Root finding
291
296
  */
292
- int rf_indice; /* Indice of state variable to use in root finding (ignored if not enabled) */
297
+ int rf_index; /* Index of state variable to use in root finding (ignored if not enabled) */
293
298
  double rf_threshold; /* Threshold to use for root finding (ignored if not enabled) */
294
299
  PyObject* rf_list; /* List to store found roots in (or None if not enabled) */
295
300
  int* rf_direction; /* Direction of root crossings: 1 for up, -1 for down, 0 for no crossing. */
@@ -306,7 +311,7 @@ double realtime_start; /* time when sim run started */
306
311
  * Returns the current time as given by the benchmarker.
307
312
  */
308
313
  double
309
- benchmarker_realtime()
314
+ benchmarker_realtime(void)
310
315
  {
311
316
  double val;
312
317
  PyObject* ret = PyObject_CallMethodObjArgs(benchmarker, benchmarker_time_str, NULL);
@@ -350,12 +355,14 @@ rhs(realtype t, N_Vector y, N_Vector ydot, void *user_data)
350
355
  UserData fdata;
351
356
  int i;
352
357
 
353
- /* Fixed-form pacing? Then look-up correct value of pacing variable! */
354
- if (fpacing != NULL) {
355
- pace = FSys_GetLevel(fpacing, t, &flag_fpacing);
356
- if (flag_fpacing != FSys_OK) { /* This should never happen */
357
- FSys_SetPyErr(flag_fpacing);
358
- return -1; /* Negative value signals irrecoverable error to CVODE */
358
+ /* Fixed-form pacing? Then look-up correct value of pacing variable */
359
+ for (int i = 0; i < n_pace; i++) {
360
+ if (pacing_types[i] == FIXED) {
361
+ pacing[i] = FSys_GetLevel(pacing_systems[i].fixed, t, &flag_fpacing);
362
+ if (flag_fpacing != FSys_OK) { /* This should never happen */
363
+ FSys_SetPyErr(flag_fpacing);
364
+ return -1; /* Negative value signals irrecoverable error to CVODE */
365
+ }
359
366
  }
360
367
  }
361
368
 
@@ -363,7 +370,7 @@ rhs(realtype t, N_Vector y, N_Vector ydot, void *user_data)
363
370
 
364
371
  /* Set time, pace, evaluations and realtime */
365
372
  evaluations++;
366
- Model_SetBoundVariables(model, t, pace, realtime, evaluations);
373
+ Model_SetBoundVariables(model, (realtype)t, (realtype*)pacing, (realtype)realtime, (realtype)evaluations);
367
374
 
368
375
  /* Set sensitivity parameters */
369
376
  if (model->has_sensitivities) {
@@ -416,7 +423,7 @@ shs(N_Vector* sy)
416
423
  int
417
424
  rf_function(realtype t, N_Vector y, realtype *gout, void *user_data)
418
425
  {
419
- gout[0] = NV_Ith_S(y, rf_indice) - rf_threshold;
426
+ gout[0] = NV_Ith_S(y, rf_index) - rf_threshold;
420
427
  return 0;
421
428
  }
422
429
 
@@ -424,9 +431,9 @@ rf_function(realtype t, N_Vector y, realtype *gout, void *user_data)
424
431
  * Cleans up after a simulation
425
432
  */
426
433
  PyObject*
427
- sim_clean()
434
+ sim_clean(void)
428
435
  {
429
- if (initialised) {
436
+ if (initialized) {
430
437
  #ifdef MYOKIT_DEBUG_PROFILING
431
438
  benchmarker_print("CP Entered sim_clean.");
432
439
  #elif defined MYOKIT_DEBUG_MESSAGES
@@ -463,8 +470,16 @@ sim_clean()
463
470
  }
464
471
 
465
472
  /* Pacing systems */
466
- ESys_Destroy(epacing); epacing = NULL;
467
- FSys_Destroy(fpacing); fpacing = NULL;
473
+ for (int i = 0; i < n_pace; i++) {
474
+ if (pacing_types[i] == FIXED) {
475
+ FSys_Destroy(pacing_systems[i].fixed);
476
+ } else if (pacing_types[i] == EVENT) {
477
+ ESys_Destroy(pacing_systems[i].event);
478
+ }
479
+ }
480
+ free(pacing_systems); pacing_systems = NULL;
481
+ free(pacing_types); pacing_types = NULL;
482
+ free(pacing); pacing = NULL;
468
483
 
469
484
  /* CModel */
470
485
  Model_Destroy(model); model = NULL;
@@ -477,7 +492,7 @@ sim_clean()
477
492
  Py_XDECREF(benchmarker_time_str); benchmarker_time_str = NULL;
478
493
 
479
494
  /* Deinitialisation complete */
480
- initialised = 0;
495
+ initialized = 0;
481
496
  }
482
497
 
483
498
  /* Return 0, allowing the construct
@@ -515,7 +530,7 @@ py_sim_clean(PyObject *self, PyObject *args)
515
530
  }
516
531
 
517
532
  /*
518
- * Initialise a run.
533
+ * Initialize a run.
519
534
  * Called by the Python code's run(), followed by several calls to sim_step().
520
535
  */
521
536
  PyObject*
@@ -531,20 +546,27 @@ sim_init(PyObject *self, PyObject *args)
531
546
  ESys_Flag flag_epacing;
532
547
  FSys_Flag flag_fpacing;
533
548
 
549
+ /* Pacing systems */
550
+ ESys epacing;
551
+ FSys fpacing;
552
+
534
553
  /* General purpose ints for iterating */
535
554
  int i, j;
536
555
 
537
556
  /* Log the first point? Only happens if not continuing from a log */
538
557
  int log_first_point;
539
558
 
559
+ /* Proposed next logging or pacing point */
560
+ double t_proposed;
561
+
540
562
  /* Python objects, and a python list index variable */
541
563
  Py_ssize_t pos;
542
564
  PyObject *val;
543
565
  PyObject *ret;
544
566
 
545
- /* Check if already initialised */
546
- if (initialised) {
547
- PyErr_SetString(PyExc_Exception, "Simulation already initialised.");
567
+ /* Check if already initialized */
568
+ if (initialized) {
569
+ PyErr_SetString(PyExc_Exception, "Simulation already initialized.");
548
570
  return 0;
549
571
  }
550
572
 
@@ -557,8 +579,9 @@ sim_init(PyObject *self, PyObject *args)
557
579
  /* Set all global pointers to null */
558
580
  /* Model and pacing */
559
581
  model = NULL;
560
- epacing = NULL;
561
- fpacing = NULL;
582
+ pacing_types = NULL;
583
+ pacing_systems = NULL;
584
+ pacing = NULL;
562
585
  /* User data and parameter scaling */
563
586
  udata = NULL;
564
587
  pbar = NULL;
@@ -586,8 +609,8 @@ sim_init(PyObject *self, PyObject *args)
586
609
  sundials_context = NULL;
587
610
  #endif
588
611
 
589
- /* Check input arguments 0123456789012345678 */
590
- if (!PyArg_ParseTuple(args, "ddOOOOOOOOdOOidOOi",
612
+ /* Check input arguments 01234567890123456 */
613
+ if (!PyArg_ParseTuple(args, "ddOOOOOOOdOOidOOi",
591
614
  &tmin, /* 0. Float: initial time */
592
615
  &tmax, /* 1. Float: final time */
593
616
  &state_py, /* 2. List: initial and final state */
@@ -595,24 +618,23 @@ sim_init(PyObject *self, PyObject *args)
595
618
  &bound_py, /* 4. List: store final bound variables here */
596
619
  &literals, /* 5. List: literal constant values */
597
620
  &parameters, /* 6. List: parameter values */
598
- &eprotocol, /* 7. Event-based protocol */
599
- &fprotocol, /* 8. Fixed-form protocol (tuple) */
600
- &log_dict, /* 9. DataLog */
601
- &log_interval, /* 10. Float: log interval, or 0 */
602
- &log_times, /* 11. List of logging times, or None */
603
- &sens_list, /* 12. List to store sensitivities in */
604
- &rf_indice, /* 13. Int: root-finding state variable */
605
- &rf_threshold, /* 14. Float: root-finding threshold */
606
- &rf_list, /* 15. List to store roots in or None */
607
- &benchmarker, /* 16. myokit.tools.Benchmarker object */
608
- &log_realtime /* 17. Int: 1 if logging real time */
621
+ &protocols, /* 7. Event-based or fixed protocols */
622
+ &log_dict, /* 8. DataLog */
623
+ &log_interval, /* 9. Float: log interval, or 0 */
624
+ &log_times, /* 10. List of logging times, or None */
625
+ &sens_list, /* 11. List to store sensitivities in */
626
+ &rf_index, /* 12. Int: root-finding state variable */
627
+ &rf_threshold, /* 13. Float: root-finding threshold */
628
+ &rf_list, /* 14. List to store roots in or None */
629
+ &benchmarker, /* 15. myokit.tools.Benchmarker object */
630
+ &log_realtime /* 16. Int: 1 if logging real time */
609
631
  )) {
610
632
  PyErr_SetString(PyExc_Exception, "Incorrect input arguments.");
611
633
  return 0;
612
634
  }
613
635
 
614
- /* Now officialy initialised */
615
- initialised = 1;
636
+ /* Now officialy initialized */
637
+ initialized = 1;
616
638
 
617
639
  /*************************************************************************
618
640
  From this point on, no more direct returning! Use sim_clean()
@@ -899,7 +921,7 @@ sim_init(PyObject *self, PyObject *args)
899
921
  if (udata == 0) {
900
922
  return sim_cleanx(PyExc_Exception, "Unable to create user data object to store parameter values.");
901
923
  }
902
- udata->p = (realtype*)malloc(sizeof(realtype) * model->ns_independents);
924
+ udata->p = (realtype*)malloc((size_t)model->ns_independents * sizeof(realtype));
903
925
  if (udata->p == 0) {
904
926
  return sim_cleanx(PyExc_Exception, "Unable to allocate space to store parameter values.");
905
927
  }
@@ -915,7 +937,7 @@ sim_init(PyObject *self, PyObject *args)
915
937
 
916
938
  /* Create parameter scaling vector, for error control */
917
939
  /* TODO: Get this from the Python code ? */
918
- pbar = (realtype*)malloc(sizeof(realtype) * model->ns_independents);
940
+ pbar = (realtype*)malloc((size_t)model->ns_independents * sizeof(realtype));
919
941
  if (pbar == NULL) {
920
942
  return sim_cleanx(PyExc_Exception, "Unable to allocate space to store parameter scales.");
921
943
  }
@@ -929,48 +951,74 @@ sim_init(PyObject *self, PyObject *args)
929
951
  }
930
952
 
931
953
  /*
932
- * Set up pacing system
954
+ * Set up pacing systems
933
955
  */
934
-
935
- /* Set up event-based pacing */
936
- if (eprotocol != Py_None) {
937
- epacing = ESys_Create(&flag_epacing);
938
- if (flag_epacing != ESys_OK) { ESys_SetPyErr(flag_epacing); return sim_clean(); }
939
- flag_epacing = ESys_Populate(epacing, eprotocol);
940
- if (flag_epacing != ESys_OK) { ESys_SetPyErr(flag_epacing); return sim_clean(); }
941
- flag_epacing = ESys_AdvanceTime(epacing, tmin);
942
- if (flag_epacing != ESys_OK) { ESys_SetPyErr(flag_epacing); return sim_clean(); }
943
- tnext = ESys_GetNextTime(epacing, &flag_epacing);
944
- pace = ESys_GetLevel(epacing, &flag_epacing);
945
- tnext = (tnext < tmax) ? tnext : tmax;
946
-
947
- #ifdef MYOKIT_DEBUG_PROFILING
948
- benchmarker_print("CP Created event-based pacing system.");
949
- #endif
950
- } else {
951
- tnext = tmax;
956
+ n_pace = 0;
957
+ if (protocols != Py_None) {
958
+ if (!PyList_Check(protocols)) {
959
+ return sim_cleanx(PyExc_TypeError, "'protocols' must be a list.");
960
+ }
961
+ n_pace = (int)PyList_Size(protocols);
962
+ }
963
+ pacing_systems = (union PSys*)malloc((size_t)n_pace * sizeof(union PSys));
964
+ if (pacing_systems == NULL) {
965
+ return sim_cleanx(PyExc_Exception, "Unable to allocate space to store pacing systems.");
966
+ }
967
+ pacing_types = (enum PSysType *)malloc((size_t)n_pace * sizeof(enum PSysType));
968
+ if (pacing_types == NULL) {
969
+ return sim_cleanx(PyExc_Exception, "Unable to allocate space to store pacing types.");
970
+ }
971
+ pacing = (realtype*)malloc((size_t)n_pace * sizeof(realtype));
972
+ if (pacing == NULL) {
973
+ return sim_cleanx(PyExc_Exception, "Unable to allocate space to store pacing values.");
952
974
  }
975
+ Model_SetupPacing(model, n_pace);
953
976
 
954
- /* Set up fixed-form pacing */
955
- if (eprotocol == Py_None && fprotocol != Py_None) {
956
- /* Check 'protocol' is tuple (times, values) */
957
- if (!PyTuple_Check(fprotocol)) {
958
- return sim_cleanx(PyExc_TypeError, "Fixed-form pacing protocol should be tuple or None.");
959
- }
960
- if (PyTuple_Size(fprotocol) != 2) {
961
- return sim_cleanx(PyExc_ValueError, "Fixed-form pacing protocol tuple should have size 2.");
962
- }
963
- /* Create fixed-form pacing object and populate */
964
- fpacing = FSys_Create(&flag_fpacing);
965
- if (flag_fpacing != FSys_OK) { FSys_SetPyErr(flag_fpacing); return sim_clean(); }
966
- flag_fpacing = FSys_Populate(fpacing,
967
- PyTuple_GetItem(fprotocol, 0), /* Borrowed, no decref */
968
- PyTuple_GetItem(fprotocol, 1));
969
- if (flag_fpacing != FSys_OK) { FSys_SetPyErr(flag_fpacing); return sim_clean(); }
977
+ /*
978
+ * Unless set by pacing, tnext is set to tmax
979
+ */
980
+ tnext = tmax;
970
981
 
971
- #ifdef MYOKIT_DEBUG_PROFILING
972
- benchmarker_print("CP Created fixed-form pacing system.");
973
- #endif
982
+ /*
983
+ * Set up event-based and/or fixed pacing.
984
+ */
985
+ if (protocols != Py_None) {
986
+ for (int i = 0; i < PyList_Size(protocols); i++) {
987
+ PyObject *protocol = PyList_GetItem(protocols, i);
988
+ const char* protocol_type_name = Py_TYPE(protocol)->tp_name;
989
+ if (strcmp(protocol_type_name, "Protocol") == 0) {
990
+ pacing_systems[i].event = ESys_Create(&flag_epacing);
991
+ pacing_types[i] = EVENT;
992
+ epacing = pacing_systems[i].event;
993
+ if (flag_epacing != ESys_OK) { ESys_SetPyErr(flag_epacing); return sim_clean(); }
994
+ flag_epacing = ESys_Populate(epacing, protocol);
995
+ if (flag_epacing != ESys_OK) { ESys_SetPyErr(flag_epacing); return sim_clean(); }
996
+ flag_epacing = ESys_AdvanceTime(epacing, tmin);
997
+ if (flag_epacing != ESys_OK) { ESys_SetPyErr(flag_epacing); return sim_clean(); }
998
+ t_proposed = ESys_GetNextTime(epacing, &flag_epacing);
999
+ pacing[i] = ESys_GetLevel(epacing, &flag_epacing);
1000
+ tnext = fmin(t_proposed, tnext);
1001
+
1002
+ #ifdef MYOKIT_DEBUG_PROFILING
1003
+ benchmarker_print("CP Created event-based pacing system.");
1004
+ #endif
1005
+ } else if (strcmp(protocol_type_name, "TimeSeriesProtocol") == 0) {
1006
+ pacing_systems[i].fixed = FSys_Create(&flag_fpacing);
1007
+ pacing_types[i] = FIXED;
1008
+ fpacing = pacing_systems[i].fixed;
1009
+ if (flag_fpacing != FSys_OK) { FSys_SetPyErr(flag_fpacing); return sim_clean(); }
1010
+ flag_fpacing = FSys_Populate(fpacing, protocol);
1011
+ if (flag_fpacing != FSys_OK) { FSys_SetPyErr(flag_fpacing); return sim_clean(); }
1012
+
1013
+
1014
+ #ifdef MYOKIT_DEBUG_PROFILING
1015
+ benchmarker_print("CP Created fixed-form pacing system.");
1016
+ #endif
1017
+ } else {
1018
+ printf("protocol_type_name: %s", protocol_type_name);
1019
+ return sim_cleanx(PyExc_TypeError, "Item %d in 'protocols' is not a myokit.Protocol or myokit.TimeSeriesProtocol object.", i);
1020
+ }
1021
+ }
974
1022
  }
975
1023
 
976
1024
  /*
@@ -992,7 +1040,7 @@ sim_init(PyObject *self, PyObject *args)
992
1040
  flag_cvode = CVodeSetErrHandlerFn(cvode_mem, ErrorHandler, NULL);
993
1041
  if (check_cvode_flag(&flag_cvode, "CVodeInit", 1)) return sim_clean();
994
1042
 
995
- /* Initialise solver memory, specify the rhs */
1043
+ /* Initialize solver memory, specify the rhs */
996
1044
  flag_cvode = CVodeInit(cvode_mem, rhs, t, y);
997
1045
  if (check_cvode_flag(&flag_cvode, "CVodeInit", 1)) return sim_clean();
998
1046
 
@@ -1051,7 +1099,7 @@ sim_init(PyObject *self, PyObject *args)
1051
1099
  #endif
1052
1100
 
1053
1101
  #ifdef MYOKIT_DEBUG_PROFILING
1054
- benchmarker_print("CP CVODES solver initialised.");
1102
+ benchmarker_print("CP CVODES solver initialized.");
1055
1103
  #endif
1056
1104
 
1057
1105
  /* Activate forward sensitivity computations */
@@ -1075,7 +1123,7 @@ sim_init(PyObject *self, PyObject *args)
1075
1123
  if (check_cvode_flag(&flag_cvode, "CVodeSensEEtolerances", 1)) return sim_clean();
1076
1124
 
1077
1125
  #ifdef MYOKIT_DEBUG_PROFILING
1078
- benchmarker_print("CP CVODES sensitivity methods initialised.");
1126
+ benchmarker_print("CP CVODES sensitivity methods initialized.");
1079
1127
  #endif
1080
1128
  }
1081
1129
  }
@@ -1092,10 +1140,10 @@ sim_init(PyObject *self, PyObject *args)
1092
1140
  if (check_cvode_flag(&flag_cvode, "CVodeRootInit", 1)) return sim_clean();
1093
1141
 
1094
1142
  /* Direction of root crossings, one entry per root function, but we only use 1. */
1095
- rf_direction = (int*)malloc(sizeof(int)*1);
1143
+ rf_direction = (int*)malloc(sizeof(int));
1096
1144
 
1097
1145
  #ifdef MYOKIT_DEBUG_PROFILING
1098
- benchmarker_print("CP CVODES root-finding initialised.");
1146
+ benchmarker_print("CP CVODES root-finding initialized.");
1099
1147
  #endif
1100
1148
  }
1101
1149
 
@@ -1111,10 +1159,10 @@ sim_init(PyObject *self, PyObject *args)
1111
1159
  }
1112
1160
 
1113
1161
  /* Set up logging */
1114
- flag_model = Model_InitialiseLogging(model, log_dict);
1162
+ flag_model = Model_InitializeLogging(model, log_dict);
1115
1163
  if (flag_model != Model_OK) { Model_SetPyErr(flag_model); return sim_clean(); }
1116
1164
  #ifdef MYOKIT_DEBUG_PROFILING
1117
- benchmarker_print("CP Logging initialised.");
1165
+ benchmarker_print("CP Logging initialized.");
1118
1166
  #endif
1119
1167
 
1120
1168
  /* Check logging list for sensitivities */
@@ -1211,7 +1259,7 @@ sim_init(PyObject *self, PyObject *args)
1211
1259
  }
1212
1260
 
1213
1261
  #ifdef MYOKIT_DEBUG_PROFILING
1214
- benchmarker_print("CP Logging times and strategy initialised.");
1262
+ benchmarker_print("CP Logging times and strategy initialized.");
1215
1263
  #endif
1216
1264
 
1217
1265
  /*
@@ -1242,8 +1290,8 @@ sim_step(PyObject *self, PyObject *args)
1242
1290
  /* Number of integration steps taken in this call */
1243
1291
  int steps_taken = 0;
1244
1292
 
1245
- /* Proposed next logging point */
1246
- double proposed_tlog;
1293
+ /* Proposed next logging or pacing point */
1294
+ double t_proposed;
1247
1295
 
1248
1296
  /* Multi-purpose Python objects */
1249
1297
  PyObject *val;
@@ -1288,9 +1336,11 @@ sim_step(PyObject *self, PyObject *args)
1288
1336
  /* PyList_SetItem steals a reference: no need to decref the double! */
1289
1337
  }
1290
1338
  PyList_SetItem(bound_py, 0, PyFloat_FromDouble(tlast));
1291
- PyList_SetItem(bound_py, 1, PyFloat_FromDouble(pace));
1292
- PyList_SetItem(bound_py, 2, PyFloat_FromDouble(realtime));
1293
- PyList_SetItem(bound_py, 3, PyFloat_FromDouble(evaluations));
1339
+ PyList_SetItem(bound_py, 1, PyFloat_FromDouble(realtime));
1340
+ PyList_SetItem(bound_py, 2, PyFloat_FromDouble((double)evaluations));
1341
+ for (int i = 0; i < n_pace; i++) {
1342
+ PyList_SetItem(bound_py, 3 + i, PyFloat_FromDouble(pacing[i]));
1343
+ }
1294
1344
  return sim_clean();
1295
1345
  }
1296
1346
 
@@ -1435,7 +1485,7 @@ sim_step(PyObject *self, PyObject *args)
1435
1485
  if (ilog < PySequence_Size(log_times)) {
1436
1486
  val = PySequence_GetItem(log_times, ilog); /* New reference */
1437
1487
  if (PyFloat_Check(val)) {
1438
- proposed_tlog = PyFloat_AsDouble(val);
1488
+ t_proposed = PyFloat_AsDouble(val);
1439
1489
  Py_DECREF(val);
1440
1490
  } else if (PyNumber_Check(val)) {
1441
1491
  ret = PyNumber_Float(val); /* New reference */
@@ -1443,17 +1493,17 @@ sim_step(PyObject *self, PyObject *args)
1443
1493
  if (ret == NULL) {
1444
1494
  return sim_cleanx(PyExc_ValueError, "Unable to cast entry in 'log_times' to float.");
1445
1495
  } else {
1446
- proposed_tlog = PyFloat_AsDouble(ret);
1496
+ t_proposed = PyFloat_AsDouble(ret);
1447
1497
  Py_DECREF(ret);
1448
1498
  }
1449
1499
  } else {
1450
1500
  Py_DECREF(val);
1451
1501
  return sim_cleanx(PyExc_ValueError, "Entries in 'log_times' must be floats.");
1452
1502
  }
1453
- if (proposed_tlog < tlog) {
1503
+ if (t_proposed < tlog) {
1454
1504
  return sim_cleanx(PyExc_ValueError, "Values in log_times must be non-decreasing.");
1455
1505
  }
1456
- tlog = proposed_tlog;
1506
+ tlog = t_proposed;
1457
1507
  ilog++;
1458
1508
  val = NULL;
1459
1509
  } else {
@@ -1469,14 +1519,18 @@ sim_step(PyObject *self, PyObject *args)
1469
1519
  * At this point we have logged everything _before_ time t, so it
1470
1520
  * is safe to update the pacing mechanism to time t.
1471
1521
  */
1472
- if (epacing != NULL) {
1473
- flag_epacing = ESys_AdvanceTime(epacing, t);
1474
- if (flag_epacing != ESys_OK) {
1475
- ESys_SetPyErr(flag_epacing); return sim_clean();
1522
+ tnext = tmax;
1523
+ for (int i = 0; i < n_pace; i++) {
1524
+ if (pacing_types[i] == EVENT) {
1525
+ ESys epacing = pacing_systems[i].event;
1526
+ flag_epacing = ESys_AdvanceTime(epacing, t);
1527
+ if (flag_epacing != ESys_OK) {
1528
+ ESys_SetPyErr(flag_epacing); return sim_clean();
1529
+ }
1530
+ t_proposed = ESys_GetNextTime(epacing, NULL);
1531
+ tnext = fmin(tnext, t_proposed);
1532
+ pacing[i] = ESys_GetLevel(epacing, NULL);
1476
1533
  }
1477
- tnext = ESys_GetNextTime(epacing, NULL);
1478
- tnext = (tnext < tmax) ? tnext : tmax;
1479
- pace = ESys_GetLevel(epacing, NULL);
1480
1534
  }
1481
1535
 
1482
1536
  /* Dynamic logging: Log every visited point */
@@ -1500,7 +1554,7 @@ sim_step(PyObject *self, PyObject *args)
1500
1554
  } else if (model->logging_bound) {
1501
1555
  /* Logging bounds but not derivs or inters: No need to run
1502
1556
  full rhs, just update bound variables */
1503
- Model_SetBoundVariables(model, t, pace, realtime, evaluations);
1557
+ Model_SetBoundVariables(model, (realtype)t, (realtype*)pacing, (realtype)realtime, (realtype)evaluations);
1504
1558
  }
1505
1559
 
1506
1560
  /* Write to log */
@@ -1584,9 +1638,11 @@ sim_step(PyObject *self, PyObject *args)
1584
1638
 
1585
1639
  /* Set bound variable values */
1586
1640
  PyList_SetItem(bound_py, 0, PyFloat_FromDouble(t));
1587
- PyList_SetItem(bound_py, 1, PyFloat_FromDouble(pace));
1588
- PyList_SetItem(bound_py, 2, PyFloat_FromDouble(realtime));
1589
- PyList_SetItem(bound_py, 3, PyFloat_FromDouble(evaluations));
1641
+ PyList_SetItem(bound_py, 1, PyFloat_FromDouble(realtime));
1642
+ PyList_SetItem(bound_py, 2, PyFloat_FromDouble((double)evaluations));
1643
+ for (int i = 0; i < n_pace; i++) {
1644
+ PyList_SetItem(bound_py, 3 + i, PyFloat_FromDouble(pacing[i]));
1645
+ }
1590
1646
 
1591
1647
  #ifdef MYOKIT_DEBUG_PROFILING
1592
1648
  benchmarker_print("CP Set final state and bound variable values.");
@@ -1606,7 +1662,8 @@ sim_eval_derivatives(PyObject *self, PyObject *args)
1606
1662
  int i;
1607
1663
  int success;
1608
1664
  double time_in;
1609
- double pace_in;
1665
+ PyObject *pace_in;
1666
+ double *pacing_in;
1610
1667
  Model model;
1611
1668
  Model_Flag flag_model;
1612
1669
  PyObject *state;
@@ -1620,9 +1677,9 @@ sim_eval_derivatives(PyObject *self, PyObject *args)
1620
1677
 
1621
1678
  /* Check input arguments */
1622
1679
  /* Check input arguments 0123456789ABCDEF*/
1623
- if (!PyArg_ParseTuple(args, "ddOOOO",
1680
+ if (!PyArg_ParseTuple(args, "dOOOOO",
1624
1681
  &time_in, /* 0. Float: time */
1625
- &pace_in, /* 1. Float: pace */
1682
+ &pace_in, /* 1. List: pace */
1626
1683
  &state, /* 2. List: state */
1627
1684
  &deriv, /* 3. List: store derivatives here */
1628
1685
  &literals, /* 4. List: literal constant values */
@@ -1634,6 +1691,10 @@ sim_eval_derivatives(PyObject *self, PyObject *args)
1634
1691
  }
1635
1692
 
1636
1693
  /* Check lists are sequences */
1694
+ if (!PyList_Check(pace_in)) {
1695
+ PyErr_SetString(PyExc_Exception, "Pace argument must be a list.");
1696
+ return 0;
1697
+ }
1637
1698
  if (!PyList_Check(state)) {
1638
1699
  PyErr_SetString(PyExc_Exception, "State argument must be a list.");
1639
1700
  return 0;
@@ -1665,8 +1726,25 @@ sim_eval_derivatives(PyObject *self, PyObject *args)
1665
1726
  goto error;
1666
1727
  }
1667
1728
 
1729
+ flag_model = Model_SetupPacing(model, n_pace);
1730
+ if (flag_model != Model_OK) {
1731
+ Model_SetPyErr(flag_model);
1732
+ goto error;
1733
+ }
1734
+
1735
+ /* Set pacing values */
1736
+ pacing_in = (double*)malloc((size_t)n_pace * sizeof(double));
1737
+ for (int i = 0; i < n_pace; i++) {
1738
+ val = PyList_GetItem(pace_in, i); /* Don't decref */
1739
+ if (!PyFloat_Check(val)) {
1740
+ PyErr_Format(PyExc_Exception, "Item %d in pace vector is not a float.", i);
1741
+ goto error;
1742
+ }
1743
+ pacing_in[i] = PyFloat_AsDouble(val);
1744
+ }
1745
+
1668
1746
  /* Set bound variables */
1669
- Model_SetBoundVariables(model, time_in, pace_in, 0, 0);
1747
+ Model_SetBoundVariables(model, (realtype)time_in, (realtype*)pacing_in, 0, 0);
1670
1748
 
1671
1749
  /* Set literal values */
1672
1750
  for (i=0; i<model->n_literals; i++) {