myokit 1.35.4__py3-none-any.whl → 1.36.1__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 +5 -3
- myokit/__main__.py +9 -159
- myokit/_config.py +2 -2
- myokit/_expressions.py +6 -6
- myokit/_model_api.py +11 -7
- myokit/_myokit_version.py +1 -1
- myokit/_protocol.py +4 -0
- myokit/_sim/__init__.py +1 -0
- myokit/_sim/cvodessim.c +321 -177
- myokit/_sim/cvodessim.py +107 -43
- myokit/_sim/mcl.h +54 -0
- myokit/formats/__init__.py +63 -12
- myokit/formats/ansic/__init__.py +2 -1
- myokit/formats/ansic/_ewriter.py +159 -40
- myokit/formats/cpp/_ewriter.py +12 -1
- myokit/formats/cuda/_ewriter.py +15 -51
- myokit/formats/easyml/_ewriter.py +26 -54
- myokit/formats/heka/_patchmaster.py +15 -3
- myokit/formats/latex/_ewriter.py +103 -88
- myokit/formats/latex/_exporter.py +1 -1
- myokit/formats/mathml/_ewriter.py +2 -2
- myokit/formats/matlab/_ewriter.py +50 -28
- myokit/formats/opencl/_ewriter.py +61 -78
- myokit/formats/python/_ewriter.py +81 -50
- myokit/formats/stan/_ewriter.py +29 -37
- myokit/gui/source.py +1 -1
- myokit/lib/hh.py +3 -0
- myokit/lib/markov.py +6 -0
- myokit/tests/__init__.py +70 -0
- myokit/tests/data/decker.model +59 -59
- myokit/tests/test_formats.py +115 -7
- myokit/tests/test_formats_ansic.py +344 -0
- myokit/tests/test_formats_axon.py +17 -0
- myokit/tests/test_formats_cpp.py +97 -0
- myokit/tests/test_formats_cuda.py +226 -0
- myokit/tests/test_formats_easyml.py +169 -152
- myokit/tests/{test_formats_exporters.py → test_formats_exporters_run.py} +1 -69
- myokit/tests/test_formats_html.py +1 -3
- myokit/tests/test_formats_latex.py +211 -0
- myokit/tests/test_formats_mathml_content.py +13 -0
- myokit/tests/test_formats_mathml_presentation.py +54 -42
- myokit/tests/test_formats_matlab.py +218 -0
- myokit/tests/test_formats_opencl.py +206 -380
- myokit/tests/test_formats_python.py +557 -0
- myokit/tests/test_formats_stan.py +175 -0
- myokit/tests/test_formats_sympy.py +9 -2
- myokit/tests/test_lib_hh.py +36 -0
- myokit/tests/test_lib_plots.py +0 -16
- myokit/tests/test_model.py +21 -1
- myokit/tests/test_simulation_cvodes.py +137 -56
- myokit/tools.py +3 -2
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/LICENSE.txt +1 -1
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/METADATA +19 -8
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/RECORD +57 -52
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/WHEEL +1 -1
- myokit/tests/test_formats_expression_writers.py +0 -1281
- myokit/tests/test_formats_importers.py +0 -53
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/entry_points.txt +0 -0
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/top_level.txt +0 -0
myokit/_sim/cvodessim.py
CHANGED
|
@@ -238,6 +238,7 @@ class Simulation(myokit.CModule):
|
|
|
238
238
|
|
|
239
239
|
# Last state reached before error
|
|
240
240
|
self._error_state = None
|
|
241
|
+
self._error_inputs = None
|
|
241
242
|
|
|
242
243
|
# Starting time
|
|
243
244
|
self._time = 0
|
|
@@ -387,6 +388,28 @@ class Simulation(myokit.CModule):
|
|
|
387
388
|
finally:
|
|
388
389
|
myokit.tools.rmtree(d_build, silent=True)
|
|
389
390
|
|
|
391
|
+
def crash_inputs(self):
|
|
392
|
+
"""
|
|
393
|
+
If the last call to :meth:`Simulation.pre()` or
|
|
394
|
+
:meth:`Simulation.run()` resulted in an error, this will return the
|
|
395
|
+
last "inputs" (time, pace, etc) reached during that simulation.
|
|
396
|
+
|
|
397
|
+
Will return ``None`` if no simulation was run or the simulation did not
|
|
398
|
+
result in an error.
|
|
399
|
+
"""
|
|
400
|
+
return dict(self._error_inputs) if self._error_inputs else None
|
|
401
|
+
|
|
402
|
+
def crash_state(self):
|
|
403
|
+
"""
|
|
404
|
+
If the last call to :meth:`Simulation.pre()` or
|
|
405
|
+
:meth:`Simulation.run()` resulted in an error, this will return the
|
|
406
|
+
last state reached during that simulation.
|
|
407
|
+
|
|
408
|
+
Will return ``None`` if no simulation was run or the simulation did not
|
|
409
|
+
result in an error.
|
|
410
|
+
"""
|
|
411
|
+
return list(self._error_state) if self._error_state else None
|
|
412
|
+
|
|
390
413
|
def default_state(self):
|
|
391
414
|
"""
|
|
392
415
|
Returns the default state.
|
|
@@ -402,60 +425,83 @@ class Simulation(myokit.CModule):
|
|
|
402
425
|
return [list(x) for x in self._s_default_state]
|
|
403
426
|
return None
|
|
404
427
|
|
|
405
|
-
def
|
|
428
|
+
def eval_derivatives(self, y=None, pacing=None):
|
|
406
429
|
"""
|
|
407
|
-
|
|
408
|
-
:meth:`Simulation.run()` resulted in an error, this will return the
|
|
409
|
-
last state reached during that simulation.
|
|
430
|
+
Deprecated alias of :meth:`evaluate_derivatives`.
|
|
410
431
|
|
|
411
|
-
|
|
412
|
-
|
|
432
|
+
Note that while :meth:`eval_derivatives` used the :meth:`state()` as
|
|
433
|
+
default, the new method uses :meth:`default_state()`.
|
|
413
434
|
"""
|
|
414
|
-
|
|
435
|
+
# Deprecated on 2024-03-08
|
|
436
|
+
import warnings
|
|
437
|
+
warnings.warn(
|
|
438
|
+
'The method `myokit.Simulation.eval_derivatives` is deprecated,'
|
|
439
|
+
' please use `evaluate_derivatives(state=sim.state())` instead.'
|
|
440
|
+
)
|
|
441
|
+
if y is None:
|
|
442
|
+
y = self.state()
|
|
443
|
+
return self.evaluate_derivatives(y, pacing)
|
|
415
444
|
|
|
416
|
-
def
|
|
445
|
+
def evaluate_derivatives(self, state=None, inputs=None):
|
|
417
446
|
"""
|
|
418
|
-
Evaluates and returns the state derivatives
|
|
447
|
+
Evaluates and returns the state derivatives for the given ``state`` and
|
|
448
|
+
``inputs``.
|
|
449
|
+
|
|
450
|
+
If no ``state`` is given, the value returned by :meth:`default_state()`
|
|
451
|
+
is used.
|
|
419
452
|
|
|
420
|
-
|
|
421
|
-
|
|
453
|
+
An optional dict ``inputs`` can be passed in to set the values of
|
|
454
|
+
time and other inputs (e.g. pacing values). If "time" is not set in
|
|
455
|
+
``inputs``, the value 0 will be used, regardless of the current
|
|
456
|
+
simulation. Similarly, if pacing values are not set in ``inputs``, 0
|
|
457
|
+
will be used regardless of the protocols or the current time.
|
|
422
458
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
it's value will be set to 0.
|
|
459
|
+
If any variables have been changed with `set_constant` their new value
|
|
460
|
+
will be used.
|
|
426
461
|
"""
|
|
427
462
|
# Get state
|
|
428
|
-
if
|
|
429
|
-
y = list(self.
|
|
463
|
+
if state is None:
|
|
464
|
+
y = list(self._default_state)
|
|
430
465
|
else:
|
|
431
|
-
y = self._model.map_to_state(
|
|
466
|
+
y = self._model.map_to_state(state)
|
|
432
467
|
|
|
468
|
+
# Get inputs
|
|
469
|
+
time = realtime = evaluations = 0
|
|
433
470
|
pacing_values = [0.0] * len(self._pacing_labels)
|
|
434
|
-
if
|
|
435
|
-
|
|
471
|
+
if inputs is not None:
|
|
472
|
+
if 'time' in inputs:
|
|
473
|
+
time = float(inputs['time'])
|
|
474
|
+
del inputs['time']
|
|
475
|
+
if 'realtime' in inputs: # User really shouldn't, but can do this
|
|
476
|
+
realtime = float(inputs['realtime'])
|
|
477
|
+
del inputs['realtime']
|
|
478
|
+
if 'evaluations' in inputs:
|
|
479
|
+
evaluations = float(inputs['evaluations'])
|
|
480
|
+
del inputs['evaluations']
|
|
481
|
+
for k, v in inputs.items():
|
|
436
482
|
try:
|
|
437
483
|
ki = self._pacing_labels.index(k)
|
|
438
|
-
pacing_values[ki] = float(v)
|
|
439
484
|
except ValueError:
|
|
440
|
-
|
|
485
|
+
raise ValueError(f'Unknown binding or pacing label `{k}`.')
|
|
486
|
+
pacing_values[ki] = float(v)
|
|
487
|
+
|
|
488
|
+
# Literals and parameters: Can be changed with set_constant
|
|
489
|
+
literals = list(self._literals.values())
|
|
490
|
+
parameters = list(self._parameters.values())
|
|
441
491
|
|
|
442
492
|
# Create space to store derivatives
|
|
443
493
|
dy = list(self._state)
|
|
444
494
|
|
|
445
495
|
# Evaluate and return
|
|
446
|
-
self._sim.
|
|
447
|
-
# 0. Time
|
|
448
|
-
|
|
449
|
-
#
|
|
450
|
-
|
|
451
|
-
#
|
|
452
|
-
|
|
453
|
-
#
|
|
454
|
-
dy,
|
|
455
|
-
# 4. Literal values
|
|
456
|
-
list(self._literals.values()),
|
|
457
|
-
# 5. Parameter values
|
|
458
|
-
list(self._parameters.values()),
|
|
496
|
+
self._sim.evaluate_derivatives(
|
|
497
|
+
time, # 0. Time
|
|
498
|
+
pacing_values, # 1. Pacing values
|
|
499
|
+
realtime, # 2. Realtime
|
|
500
|
+
evaluations, # 3. Evaluations
|
|
501
|
+
literals, # 4. Literals
|
|
502
|
+
parameters, # 5. Parameters
|
|
503
|
+
y, # 6. State
|
|
504
|
+
dy, # 7. Derivatives (out)
|
|
459
505
|
)
|
|
460
506
|
return dy
|
|
461
507
|
|
|
@@ -473,6 +519,21 @@ class Simulation(myokit.CModule):
|
|
|
473
519
|
"""
|
|
474
520
|
return self._sim.number_of_steps()
|
|
475
521
|
|
|
522
|
+
def last_state(self):
|
|
523
|
+
"""
|
|
524
|
+
If the last call to :meth:`Simulation.pre()` or
|
|
525
|
+
:meth:`Simulation.run()` resulted in an error, this will return the
|
|
526
|
+
last state reached during that simulation.
|
|
527
|
+
|
|
528
|
+
Will return ``None`` if no simulation was run or the simulation did not
|
|
529
|
+
result in an error.
|
|
530
|
+
"""
|
|
531
|
+
# Deprecated on 2024-03-08
|
|
532
|
+
import warnings
|
|
533
|
+
warnings.warn('The method `myokit.Simulation.last_state` is'
|
|
534
|
+
' deprecated. Please use `crash_state` instead.')
|
|
535
|
+
return self.crash_state()
|
|
536
|
+
|
|
476
537
|
def pre(self, duration, progress=None, msg='Pre-pacing simulation'):
|
|
477
538
|
"""
|
|
478
539
|
This method can be used to perform an unlogged simulation, typically to
|
|
@@ -690,6 +751,7 @@ class Simulation(myokit.CModule):
|
|
|
690
751
|
|
|
691
752
|
# Reset error state
|
|
692
753
|
self._error_state = None
|
|
754
|
+
self._error_inputs = None
|
|
693
755
|
|
|
694
756
|
# Simulation times
|
|
695
757
|
if duration < 0:
|
|
@@ -847,26 +909,28 @@ class Simulation(myokit.CModule):
|
|
|
847
909
|
|
|
848
910
|
# Store error state
|
|
849
911
|
self._error_state = state
|
|
912
|
+
self._error_inputs = {'time': bound[0]}
|
|
913
|
+
for i, label in enumerate(('realtime', 'evaluations')):
|
|
914
|
+
if self._model.binding(label) is not None:
|
|
915
|
+
self._error_inputs[label] = bound[1 + i]
|
|
916
|
+
for i, label in enumerate(self._pacing_labels):
|
|
917
|
+
self._error_inputs[label] = bound[3 + i]
|
|
850
918
|
|
|
851
919
|
# Create long error message
|
|
852
920
|
txt = ['A numerical error occurred during simulation at'
|
|
853
|
-
' t = ' + str(
|
|
921
|
+
' t = ' + myokit.float.str(bound[0]) + '.',
|
|
922
|
+
'Last reached state: ']
|
|
854
923
|
txt.extend([' ' + x for x
|
|
855
924
|
in self._model.format_state(state).splitlines()])
|
|
856
925
|
txt.append('Inputs for binding:')
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
txt.append(' evaluations = ' + myokit.float.str(bound[2]))
|
|
860
|
-
for i, label in enumerate(self._pacing_labels):
|
|
861
|
-
txt.append(
|
|
862
|
-
' ' + label + ' = ' + myokit.float.str(bound[3 + i])
|
|
863
|
-
)
|
|
926
|
+
for key, value in self._error_inputs.items():
|
|
927
|
+
txt.append(f' {key} = {myokit.float.str(value)}')
|
|
864
928
|
txt.append(str(e))
|
|
865
929
|
|
|
866
930
|
# Check if state derivatives can be evaluated in Python, if
|
|
867
931
|
# not, add the error to the error message.
|
|
868
932
|
try:
|
|
869
|
-
self._model.evaluate_derivatives(state)
|
|
933
|
+
self._model.evaluate_derivatives(state, self._error_inputs)
|
|
870
934
|
except Exception as en:
|
|
871
935
|
txt.append(str(en))
|
|
872
936
|
|
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/formats/__init__.py
CHANGED
|
@@ -24,6 +24,9 @@ _EWRITERS = None
|
|
|
24
24
|
class Exporter:
|
|
25
25
|
"""
|
|
26
26
|
Abstract base class for exporters.
|
|
27
|
+
|
|
28
|
+
An exporter is a class that can produce model code or runnable code from a
|
|
29
|
+
Myokit model or a model & protocol combination (runnable).
|
|
27
30
|
"""
|
|
28
31
|
def __init__(self):
|
|
29
32
|
super().__init__()
|
|
@@ -106,15 +109,46 @@ def exporters():
|
|
|
106
109
|
|
|
107
110
|
class ExpressionWriter:
|
|
108
111
|
"""
|
|
109
|
-
Base class for expression writers
|
|
110
|
-
|
|
112
|
+
Base class for expression writers.
|
|
113
|
+
|
|
114
|
+
Expression writers take :class:`myokit.Expression` objects and produce code
|
|
115
|
+
in some language other than Myokit.
|
|
116
|
+
|
|
117
|
+
When implementing an expression writer, there are a few important edge
|
|
118
|
+
cases to consider:
|
|
119
|
+
|
|
120
|
+
1. Simplifications must not be made at this stage. For example, if the user
|
|
121
|
+
writes "+1" instead of "1", the code should output "+1". If
|
|
122
|
+
simplifications are desired, these should be made at an earlier stage,
|
|
123
|
+
when everything is still in symbolic form.
|
|
124
|
+
|
|
125
|
+
2. Powers (exponentiation) can be left or right-associative. In Myokit and,
|
|
126
|
+
for example, in Matlab, the expression ``a^b^c`` is interpreted as
|
|
127
|
+
``(a^b)^c``. By in Python (and e.g. in many spreadsheet applications)
|
|
128
|
+
``a**b**c`` is interpreted as ``a**(b**c)``, necessitating a different
|
|
129
|
+
bracket-adding logic than used in Myokit.
|
|
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 ``++``,
|
|
140
|
+
so the expression ``--x`` is interpreted as ``-(-x)``. This is the same
|
|
141
|
+
in Python. But in C-based languages, this is interpreted as a decrement
|
|
142
|
+
operator so care must be taken to add extra brackets.
|
|
143
|
+
|
|
111
144
|
"""
|
|
112
145
|
def __init__(self):
|
|
113
146
|
self._op_map = self._build_op_map()
|
|
114
147
|
|
|
115
148
|
def _build_op_map(self):
|
|
116
149
|
"""
|
|
117
|
-
Returns a mapping from
|
|
150
|
+
Returns a mapping from Myokit operators to lambda functions on
|
|
151
|
+
expressions.
|
|
118
152
|
"""
|
|
119
153
|
return {
|
|
120
154
|
myokit.Name: self._ex_name,
|
|
@@ -158,15 +192,11 @@ class ExpressionWriter:
|
|
|
158
192
|
}
|
|
159
193
|
|
|
160
194
|
def eq(self, q):
|
|
161
|
-
"""
|
|
162
|
-
Converts an equation to a string
|
|
163
|
-
"""
|
|
195
|
+
""" Converts a :class:`myokit.Equation` to a string. """
|
|
164
196
|
return self.ex(q.lhs) + ' = ' + self.ex(q.rhs)
|
|
165
197
|
|
|
166
198
|
def ex(self, e):
|
|
167
|
-
"""
|
|
168
|
-
Converts an Expression to a string.
|
|
169
|
-
"""
|
|
199
|
+
""" Converts a :class:`myokit.Expression` to a string. """
|
|
170
200
|
t = type(e)
|
|
171
201
|
if t not in self._op_map: # pragma: no cover
|
|
172
202
|
raise ValueError('Unknown expression type: ' + str(t))
|
|
@@ -188,9 +218,17 @@ class ExpressionWriter:
|
|
|
188
218
|
raise NotImplementedError
|
|
189
219
|
|
|
190
220
|
def _ex_prefix_plus(self, e):
|
|
221
|
+
#
|
|
222
|
+
# Note: Spaces or brackets should be used in languages where ++ is an
|
|
223
|
+
# operator
|
|
224
|
+
#
|
|
191
225
|
raise NotImplementedError
|
|
192
226
|
|
|
193
227
|
def _ex_prefix_minus(self, e):
|
|
228
|
+
#
|
|
229
|
+
# Note: Spaces or brackets should be used in languages where -- is an
|
|
230
|
+
# operator
|
|
231
|
+
#
|
|
194
232
|
raise NotImplementedError
|
|
195
233
|
|
|
196
234
|
def _ex_plus(self, e):
|
|
@@ -212,6 +250,10 @@ class ExpressionWriter:
|
|
|
212
250
|
raise NotImplementedError
|
|
213
251
|
|
|
214
252
|
def _ex_power(self, e):
|
|
253
|
+
#
|
|
254
|
+
# Note: Most languages agree that -a**b means -(a**b)
|
|
255
|
+
# https://codeplea.com/exponentiation-associativity-options
|
|
256
|
+
#
|
|
215
257
|
raise NotImplementedError
|
|
216
258
|
|
|
217
259
|
def _ex_sqrt(self, e):
|
|
@@ -239,6 +281,11 @@ class ExpressionWriter:
|
|
|
239
281
|
raise NotImplementedError
|
|
240
282
|
|
|
241
283
|
def _ex_log(self, e):
|
|
284
|
+
#
|
|
285
|
+
# Log can have 1 or 2 arguments. If the language does not support
|
|
286
|
+
# 2-argument log, use (log(a) / log(b)), including the outer
|
|
287
|
+
# brackets (because a function is expected, which is never bracketed).
|
|
288
|
+
#
|
|
242
289
|
raise NotImplementedError
|
|
243
290
|
|
|
244
291
|
def _ex_log10(self, e):
|
|
@@ -253,10 +300,11 @@ class ExpressionWriter:
|
|
|
253
300
|
def _ex_abs(self, e):
|
|
254
301
|
raise NotImplementedError
|
|
255
302
|
|
|
256
|
-
def _ex_not(self, e):
|
|
257
|
-
raise NotImplementedError
|
|
258
|
-
|
|
259
303
|
def _ex_equal(self, e):
|
|
304
|
+
#
|
|
305
|
+
# To obtain the desired `(0 == 0) == 0` behaviour, this and other
|
|
306
|
+
# conditions are best rendered with brackets.
|
|
307
|
+
#
|
|
260
308
|
raise NotImplementedError
|
|
261
309
|
|
|
262
310
|
def _ex_not_equal(self, e):
|
|
@@ -280,6 +328,9 @@ class ExpressionWriter:
|
|
|
280
328
|
def _ex_or(self, e):
|
|
281
329
|
raise NotImplementedError
|
|
282
330
|
|
|
331
|
+
def _ex_not(self, e):
|
|
332
|
+
raise NotImplementedError
|
|
333
|
+
|
|
283
334
|
def _ex_if(self, e):
|
|
284
335
|
raise NotImplementedError
|
|
285
336
|
|
myokit/formats/ansic/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# See http://myokit.org for copyright, sharing, and licensing details.
|
|
6
6
|
#
|
|
7
7
|
from ._exporter import AnsiCExporter, AnsiCCableExporter, AnsiCEulerExporter
|
|
8
|
-
from ._ewriter import AnsiCExpressionWriter
|
|
8
|
+
from ._ewriter import AnsiCExpressionWriter, CBasedExpressionWriter # noqa
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
# Importers
|
|
@@ -28,6 +28,7 @@ def exporters():
|
|
|
28
28
|
# Expression writers
|
|
29
29
|
_ewriters = {
|
|
30
30
|
'ansic': AnsiCExpressionWriter,
|
|
31
|
+
# c-based is intended as base class only
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
|