myokit 1.36.1__py3-none-any.whl → 1.37.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 +6 -19
- myokit/_aux.py +4 -0
- myokit/_datablock.py +55 -65
- myokit/_datalog.py +42 -7
- myokit/_err.py +26 -3
- myokit/_expressions.py +241 -127
- myokit/_model_api.py +19 -13
- myokit/_myokit_version.py +1 -1
- myokit/_sim/jacobian.py +3 -3
- myokit/_sim/openclsim.py +5 -5
- myokit/_sim/rhs.py +1 -1
- myokit/formats/__init__.py +4 -9
- myokit/formats/ansic/_ewriter.py +4 -20
- myokit/formats/axon/_abf.py +11 -4
- myokit/formats/diffsl/__init__.py +60 -0
- myokit/formats/diffsl/_ewriter.py +145 -0
- myokit/formats/diffsl/_exporter.py +435 -0
- myokit/formats/heka/_patchmaster.py +345 -115
- myokit/formats/opencl/_ewriter.py +3 -42
- myokit/formats/opencl/template/minilog.py +1 -1
- myokit/formats/sympy/_ereader.py +2 -1
- myokit/formats/wcp/_wcp.py +3 -3
- myokit/gui/datalog_viewer.py +28 -9
- myokit/lib/markov.py +2 -2
- myokit/lib/plots.py +4 -4
- myokit/tests/data/formats/wcp-file-empty.wcp +0 -0
- myokit/tests/data/io/bad1d-2-no-header.zip +0 -0
- myokit/tests/data/io/bad1d-3-no-data.zip +0 -0
- myokit/tests/data/io/bad1d-4-not-a-zip.zip +1 -105
- myokit/tests/data/io/bad1d-5-bad-data-type.zip +0 -0
- myokit/tests/data/io/bad1d-6-time-too-short.zip +0 -0
- myokit/tests/data/io/bad1d-7-0d-too-short.zip +0 -0
- myokit/tests/data/io/bad1d-8-1d-too-short.zip +0 -0
- myokit/tests/data/io/bad2d-2-no-header.zip +0 -0
- myokit/tests/data/io/bad2d-3-no-data.zip +0 -0
- myokit/tests/data/io/bad2d-4-not-a-zip.zip +1 -105
- myokit/tests/data/io/bad2d-5-bad-data-type.zip +0 -0
- myokit/tests/data/io/bad2d-8-2d-too-short.zip +0 -0
- myokit/tests/data/io/block1d.mmt +187 -0
- myokit/tests/data/io/datalog-18-duplicate-keys.csv +4 -0
- myokit/tests/test_aux.py +4 -0
- myokit/tests/test_datablock.py +16 -16
- myokit/tests/test_datalog.py +24 -1
- myokit/tests/test_expressions.py +532 -251
- myokit/tests/test_formats_ansic.py +6 -18
- myokit/tests/test_formats_cpp.py +0 -5
- myokit/tests/test_formats_cuda.py +7 -15
- myokit/tests/test_formats_diffsl.py +728 -0
- myokit/tests/test_formats_easyml.py +4 -9
- myokit/tests/test_formats_exporters_run.py +3 -0
- myokit/tests/test_formats_latex.py +10 -11
- myokit/tests/test_formats_matlab.py +0 -8
- myokit/tests/test_formats_opencl.py +0 -29
- myokit/tests/test_formats_python.py +2 -19
- myokit/tests/test_formats_stan.py +0 -13
- myokit/tests/test_formats_sympy.py +3 -3
- myokit/tests/test_formats_wcp.py +15 -0
- myokit/tests/test_model.py +20 -20
- myokit/tests/test_parsing.py +19 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/METADATA +1 -1
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/RECORD +65 -58
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/LICENSE.txt +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/WHEEL +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/entry_points.txt +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/top_level.txt +0 -0
myokit/_model_api.py
CHANGED
|
@@ -716,7 +716,10 @@ class VarOwner(ModelPart, VarProvider):
|
|
|
716
716
|
If ``recursive`` is ``True``, any child variables will be deleted as
|
|
717
717
|
well.
|
|
718
718
|
|
|
719
|
-
A :class:`myokit.IntegrityError` will be raised if
|
|
719
|
+
A :class:`myokit.IntegrityError` will be raised if the variable cannot
|
|
720
|
+
be removed because other variables depend on it. (Although dependencies
|
|
721
|
+
from child variables will be ignored if ``recursive`` is set to
|
|
722
|
+
``True``).
|
|
720
723
|
"""
|
|
721
724
|
if variable.parent() != self:
|
|
722
725
|
raise ValueError(
|
|
@@ -1095,9 +1098,7 @@ class Model(ObjectWithMetaData, VarProvider):
|
|
|
1095
1098
|
raise myokit.IncompatibleUnitError(msg, var._token)
|
|
1096
1099
|
|
|
1097
1100
|
def clone(self):
|
|
1098
|
-
"""
|
|
1099
|
-
Returns a (deep) clone of this model.
|
|
1100
|
-
"""
|
|
1101
|
+
""" Returns a (deep) clone of this model. """
|
|
1101
1102
|
clone = Model()
|
|
1102
1103
|
|
|
1103
1104
|
# Copy meta data
|
|
@@ -4520,16 +4521,10 @@ class Variable(VarOwner):
|
|
|
4520
4521
|
warnings.warn('The keyword argument `state_value` is deprecated.'
|
|
4521
4522
|
' Please use `initial_value` instead.')
|
|
4522
4523
|
|
|
4523
|
-
#
|
|
4524
|
-
|
|
4525
|
-
if not isinstance(initial_value, myokit.Expression):
|
|
4526
|
-
if isinstance(initial_value, str):
|
|
4527
|
-
# Expressions are evaluated in model context
|
|
4528
|
-
initial_value = myokit.parse_expression(
|
|
4529
|
-
initial_value, context=model)
|
|
4530
|
-
elif initial_value is not None:
|
|
4531
|
-
initial_value = myokit.Number(initial_value)
|
|
4524
|
+
# Parse initial value
|
|
4525
|
+
initial_value = self._set_initial_value(initial_value, False)
|
|
4532
4526
|
|
|
4527
|
+
model = self.model()
|
|
4533
4528
|
try:
|
|
4534
4529
|
# Set lhs to derivative expression
|
|
4535
4530
|
self._lhs = myokit.Derivative(myokit.Name(self))
|
|
@@ -4854,6 +4849,9 @@ class Variable(VarOwner):
|
|
|
4854
4849
|
x.set_rhs(myokit.Plus(myokit.Number(1), myokit.Name(y)))
|
|
4855
4850
|
x.set_rhs('1 + y')
|
|
4856
4851
|
|
|
4852
|
+
Expressions used as a variable's right-hand side must be numerical:
|
|
4853
|
+
:class:`myokit.Condition` operators can not be used as RHS.
|
|
4854
|
+
|
|
4857
4855
|
Calling `set_rhs` will reset the validation status of the model this
|
|
4858
4856
|
variable belongs to.
|
|
4859
4857
|
"""
|
|
@@ -5107,6 +5105,14 @@ class Equation:
|
|
|
5107
5105
|
def __init__(self, lhs, rhs):
|
|
5108
5106
|
self._lhs = lhs
|
|
5109
5107
|
self._rhs = rhs
|
|
5108
|
+
if not isinstance(lhs, myokit.Expression):
|
|
5109
|
+
raise myokit.IntegrityError(
|
|
5110
|
+
'Both sides of an equation must be myokit.Expression objects.'
|
|
5111
|
+
f' Found {type(lhs)} for LHS.')
|
|
5112
|
+
if not isinstance(rhs, myokit.Expression):
|
|
5113
|
+
raise myokit.IntegrityError(
|
|
5114
|
+
'Both sides of an equation must be myokit.Expression objects.'
|
|
5115
|
+
f' Found {type(lhs)} for RHS.')
|
|
5110
5116
|
|
|
5111
5117
|
def __eq__(self, other):
|
|
5112
5118
|
if not isinstance(other, Equation):
|
myokit/_myokit_version.py
CHANGED
|
@@ -14,7 +14,7 @@ __release__ = True
|
|
|
14
14
|
# incompatibility
|
|
15
15
|
# - Changes to revision indicate bugfixes, tiny new features
|
|
16
16
|
# - There is no significance to odd/even numbers
|
|
17
|
-
__version_tuple__ = 1,
|
|
17
|
+
__version_tuple__ = 1, 37, 1
|
|
18
18
|
|
|
19
19
|
# String version of the version number
|
|
20
20
|
__version__ = '.'.join([str(x) for x in __version_tuple__])
|
myokit/_sim/jacobian.py
CHANGED
|
@@ -166,7 +166,7 @@ class JacobianTracer(myokit.CppModule):
|
|
|
166
166
|
self._ext.calculate(state, bound, deriv, partial)
|
|
167
167
|
# Discard derivatives
|
|
168
168
|
# Convert partial derivatives to numpy array and store
|
|
169
|
-
partial = np.
|
|
169
|
+
partial = np.asarray(partial)
|
|
170
170
|
partial = partial.reshape((ns, ns))
|
|
171
171
|
partials.append(partial)
|
|
172
172
|
partials = np.array(partials)
|
|
@@ -282,8 +282,8 @@ class JacobianCalculator(myokit.CppModule):
|
|
|
282
282
|
self._ext.calculate(state, inputs, deriv, partial)
|
|
283
283
|
|
|
284
284
|
# Create numpy versions and return
|
|
285
|
-
deriv = np.
|
|
286
|
-
partial = np.
|
|
285
|
+
deriv = np.asarray(deriv)
|
|
286
|
+
partial = np.asarray(partial).reshape((n, n))
|
|
287
287
|
return deriv, partial
|
|
288
288
|
|
|
289
289
|
def newton_root(self, x=None, accuracy=0, max_iter=50, damping=1):
|
myokit/_sim/openclsim.py
CHANGED
|
@@ -534,7 +534,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
534
534
|
lower, upper = safe_range
|
|
535
535
|
for dims in myokit._dimco(*self._dims):
|
|
536
536
|
key = '.'.join([str(x) for x in dims]) + post
|
|
537
|
-
ar = np.
|
|
537
|
+
ar = np.asarray(_log[key])
|
|
538
538
|
i = np.where(
|
|
539
539
|
(ar < lower)
|
|
540
540
|
| (ar > upper)
|
|
@@ -1080,7 +1080,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1080
1080
|
n = len(self._fields) * self._nx * self._ny
|
|
1081
1081
|
if n:
|
|
1082
1082
|
field_data = self._fields.values()
|
|
1083
|
-
field_data = [np.
|
|
1083
|
+
field_data = [np.asarray(x) for x in field_data]
|
|
1084
1084
|
field_data = np.vstack(field_data)
|
|
1085
1085
|
field_data = list(field_data.reshape(n, order='F'))
|
|
1086
1086
|
else:
|
|
@@ -1342,7 +1342,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1342
1342
|
'This method is unavailable when diffusion is disabled.')
|
|
1343
1343
|
|
|
1344
1344
|
# Check the field's size
|
|
1345
|
-
gx = np.
|
|
1345
|
+
gx = np.asarray(gx, dtype=float)
|
|
1346
1346
|
if len(self._dims) == 1:
|
|
1347
1347
|
s = self._nx - 1
|
|
1348
1348
|
if gx.shape != (s, ):
|
|
@@ -1360,7 +1360,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1360
1360
|
if gy is None:
|
|
1361
1361
|
raise ValueError(
|
|
1362
1362
|
'The argument `gy` must be set for 2-d simulations.')
|
|
1363
|
-
gy = np.
|
|
1363
|
+
gy = np.asarray(gy, dtype=float)
|
|
1364
1364
|
s = (self._ny - 1, self._nx)
|
|
1365
1365
|
if gy.shape != s:
|
|
1366
1366
|
raise ValueError(
|
|
@@ -1514,7 +1514,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1514
1514
|
if not var.is_constant():
|
|
1515
1515
|
raise ValueError('Only constants can be used for fields.')
|
|
1516
1516
|
# Check values
|
|
1517
|
-
values = np.
|
|
1517
|
+
values = np.asarray(values, dtype=float)
|
|
1518
1518
|
if len(self._dims) == 1:
|
|
1519
1519
|
if values.shape != (self._nx, ):
|
|
1520
1520
|
raise ValueError(
|
myokit/_sim/rhs.py
CHANGED
myokit/formats/__init__.py
CHANGED
|
@@ -128,15 +128,7 @@ class ExpressionWriter:
|
|
|
128
128
|
``a**b**c`` is interpreted as ``a**(b**c)``, necessitating a different
|
|
129
129
|
bracket-adding logic than used in Myokit.
|
|
130
130
|
|
|
131
|
-
3.
|
|
132
|
-
Myokit, ``0 == 0 == 0`` is a sequence of two binary operators,
|
|
133
|
-
interpreted as ``(0 == 0) == 0``. Because ``(0 == 0)`` evaluates to
|
|
134
|
-
``1``, this expression returns ``0`` (1 does not equal 0). In Python,
|
|
135
|
-
the expression ``0 == 0 == 0`` is a ternary (n-ary) operator,
|
|
136
|
-
interpreted as ``all_equal(0, 0, 0)``, which evaluates to ``1``. For
|
|
137
|
-
languages that use this convention, extra brackets must be added.
|
|
138
|
-
|
|
139
|
-
4. Myokit does not have increment or decrement operators ``--`` and ``++``,
|
|
131
|
+
3. Myokit does not have increment or decrement operators ``--`` and ``++``,
|
|
140
132
|
so the expression ``--x`` is interpreted as ``-(-x)``. This is the same
|
|
141
133
|
in Python. But in C-based languages, this is interpreted as a decrement
|
|
142
134
|
operator so care must be taken to add extra brackets.
|
|
@@ -886,6 +878,9 @@ class SweepSource:
|
|
|
886
878
|
|
|
887
879
|
Note that a source with zero recorded channels may still report a
|
|
888
880
|
non-zero number of sweeps if it can provide D/A outputs.
|
|
881
|
+
|
|
882
|
+
Similarly, formats like WCP can report zero sweeps but have a non-zero
|
|
883
|
+
channel count (if no data was recorded).
|
|
889
884
|
"""
|
|
890
885
|
raise NotImplementedError
|
|
891
886
|
|
myokit/formats/ansic/_ewriter.py
CHANGED
|
@@ -102,23 +102,14 @@ class CBasedExpressionWriter(PythonExpressionWriter):
|
|
|
102
102
|
|
|
103
103
|
def _ex_not(self, e):
|
|
104
104
|
# C conditions all have brackets, so don't add more
|
|
105
|
-
|
|
106
|
-
return f'(!{self.ex(e[0])})'
|
|
107
|
-
# But do add more if the user's being silly
|
|
108
|
-
return f'(!({self.ex(e[0])}))'
|
|
105
|
+
return f'(!{self.ex(e[0])})'
|
|
109
106
|
|
|
110
107
|
def _ex_if(self, e):
|
|
111
|
-
|
|
112
|
-
# If i is not a condtion (which always gets brackets from this writer)
|
|
113
|
-
# then add brackets
|
|
114
|
-
if not isinstance(e._i, myokit.Condition):
|
|
115
|
-
_if = f'({_if})'
|
|
116
|
-
return f'({_if} ? {_then} : {_else})'
|
|
108
|
+
return f'({self.ex(e._i)} ? {self.ex(e._t)} : {self.ex(e._e)})'
|
|
117
109
|
|
|
118
110
|
def _ex_piecewise(self, e):
|
|
119
111
|
# Render ifs; add extra bracket if not a condition (see _ex_if)
|
|
120
|
-
_ifs = [self.ex(x)
|
|
121
|
-
else f'({self.ex(x)})' for x in e._i]
|
|
112
|
+
_ifs = [self.ex(x) for x in e._i]
|
|
122
113
|
_thens = [self.ex(x) for x in e._e]
|
|
123
114
|
|
|
124
115
|
s = []
|
|
@@ -200,13 +191,7 @@ class AnsiCExpressionWriter(CBasedExpressionWriter):
|
|
|
200
191
|
#def _ex_not(self, e):
|
|
201
192
|
|
|
202
193
|
def _ex_if(self, e):
|
|
203
|
-
# Allow _fcond
|
|
204
|
-
|
|
205
194
|
_if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
|
|
206
|
-
# If i is not a condtion (which always gets brackets from this writer)
|
|
207
|
-
# then add brackets
|
|
208
|
-
if not isinstance(e._i, myokit.Condition):
|
|
209
|
-
_if = f'({_if})'
|
|
210
195
|
|
|
211
196
|
# Use if-then-else function?
|
|
212
197
|
if self._fcond is not None:
|
|
@@ -219,8 +204,7 @@ class AnsiCExpressionWriter(CBasedExpressionWriter):
|
|
|
219
204
|
# Allow _fcond
|
|
220
205
|
|
|
221
206
|
# Render ifs; add extra bracket if not a condition (see _ex_if)
|
|
222
|
-
_ifs = [self.ex(x)
|
|
223
|
-
else f'({self.ex(x)})' for x in e._i]
|
|
207
|
+
_ifs = [self.ex(x) for x in e._i]
|
|
224
208
|
_thens = [self.ex(x) for x in e._e]
|
|
225
209
|
|
|
226
210
|
s = []
|
myokit/formats/axon/_abf.py
CHANGED
|
@@ -574,8 +574,8 @@ class AbfFile(myokit.formats.SweepSource):
|
|
|
574
574
|
# Only episodic stimulation is supported.
|
|
575
575
|
if self._mode != ACMODE_EPISODIC_STIMULATION: # pragma: no cover
|
|
576
576
|
warnings.warn(
|
|
577
|
-
'Unsupported acquisition method
|
|
578
|
-
|
|
577
|
+
'Unsupported acquisition method'
|
|
578
|
+
f' {acquisition_modes[self._mode]}; unable to read D/A'
|
|
579
579
|
' channels.')
|
|
580
580
|
|
|
581
581
|
# Remaining code is all about reading D/A info for episodic
|
|
@@ -713,7 +713,7 @@ class AbfFile(myokit.formats.SweepSource):
|
|
|
713
713
|
elif t != EPOCH_DISABLED: # pragma: no cover
|
|
714
714
|
use = False
|
|
715
715
|
warnings.warn(
|
|
716
|
-
f'Unsupported epoch type: {epoch_types
|
|
716
|
+
f'Unsupported epoch type: {epoch_types[t]}')
|
|
717
717
|
break
|
|
718
718
|
elif source == DAC_DACFILEWAVEFORM: # pragma: no cover
|
|
719
719
|
# Stimulus file? Then don't use
|
|
@@ -1375,7 +1375,14 @@ class AbfFile(myokit.formats.SweepSource):
|
|
|
1375
1375
|
try:
|
|
1376
1376
|
return self._unit_cache[unit_string]
|
|
1377
1377
|
except KeyError:
|
|
1378
|
-
|
|
1378
|
+
try:
|
|
1379
|
+
unit = myokit.parse_unit(unit_string.replace(MU, 'u'))
|
|
1380
|
+
except myokit.ParseError: # pragma: no cover
|
|
1381
|
+
if unit_string == 'oC':
|
|
1382
|
+
warnings.warn('Unsupported units degrees C.')
|
|
1383
|
+
else:
|
|
1384
|
+
warnings.warn(f'Unsupported units {unit_string}.')
|
|
1385
|
+
unit = myokit.units.dimensionless
|
|
1379
1386
|
self._unit_cache[unit_string] = unit
|
|
1380
1387
|
return unit
|
|
1381
1388
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Provides DiffSL support
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Myokit.
|
|
5
|
+
# See http://myokit.org for copyright, sharing, and licensing details.
|
|
6
|
+
#
|
|
7
|
+
from ._ewriter import DiffSLExpressionWriter
|
|
8
|
+
from ._exporter import DiffSLExporter
|
|
9
|
+
|
|
10
|
+
# Importers
|
|
11
|
+
|
|
12
|
+
# Exporters
|
|
13
|
+
_exporters = {
|
|
14
|
+
'diffsl': DiffSLExporter,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def exporters():
|
|
19
|
+
"""
|
|
20
|
+
Returns a dict of all exporters available in this module.
|
|
21
|
+
"""
|
|
22
|
+
return dict(_exporters)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Expression writers
|
|
26
|
+
_ewriters = {
|
|
27
|
+
'diffsl': DiffSLExpressionWriter,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def ewriters():
|
|
32
|
+
"""
|
|
33
|
+
Returns a dict of all expression writers available in this module.
|
|
34
|
+
"""
|
|
35
|
+
return dict(_ewriters)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Language keywords
|
|
40
|
+
#
|
|
41
|
+
keywords = [
|
|
42
|
+
'abs',
|
|
43
|
+
'cos',
|
|
44
|
+
'dudt',
|
|
45
|
+
'exp',
|
|
46
|
+
'F',
|
|
47
|
+
'G',
|
|
48
|
+
'heaviside',
|
|
49
|
+
'in',
|
|
50
|
+
'log',
|
|
51
|
+
'M',
|
|
52
|
+
'out',
|
|
53
|
+
'pow',
|
|
54
|
+
'sigmoid',
|
|
55
|
+
'sin',
|
|
56
|
+
'sqrt',
|
|
57
|
+
't',
|
|
58
|
+
'tan',
|
|
59
|
+
'u',
|
|
60
|
+
]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DiffSL expression writer
|
|
3
|
+
#
|
|
4
|
+
# Supported functions:
|
|
5
|
+
# https://martinjrobins.github.io/diffsl/functions.html
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Myokit.
|
|
8
|
+
# See http://myokit.org for copyright, sharing, and licensing details.
|
|
9
|
+
#
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
from myokit import And, Equal, If, LessEqual, Log, MoreEqual, Not, Number
|
|
13
|
+
from myokit.formats.ansic import CBasedExpressionWriter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DiffSLExpressionWriter(CBasedExpressionWriter):
|
|
17
|
+
"""
|
|
18
|
+
This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` writes
|
|
19
|
+
equations for variables in DiffSL syntax.
|
|
20
|
+
|
|
21
|
+
For details of the language, see https://martinjrobins.github.io/diffsl/.
|
|
22
|
+
|
|
23
|
+
Warnings will be generated if unsupported functions are used in the model.
|
|
24
|
+
Unsupported functions: `acos`, `asin`, `atan`, `ceil`, `floor`.
|
|
25
|
+
|
|
26
|
+
Support for logic expressions is implemented with heaviside functions.
|
|
27
|
+
For example, `(a >= b)` is converted to `heaviside(a - b)`.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
# -- Literals and identifiers
|
|
35
|
+
|
|
36
|
+
# def _ex_name(self, e):
|
|
37
|
+
# def _ex_number(self, e):
|
|
38
|
+
|
|
39
|
+
# -- Functions
|
|
40
|
+
|
|
41
|
+
def _ex_abs(self, e):
|
|
42
|
+
return self._ex_function(e, 'abs')
|
|
43
|
+
|
|
44
|
+
def _ex_acos(self, e):
|
|
45
|
+
warnings.warn('Unsupported function: acos()')
|
|
46
|
+
return super()._ex_acos(e)
|
|
47
|
+
|
|
48
|
+
def _ex_asin(self, e):
|
|
49
|
+
warnings.warn('Unsupported function: asin()')
|
|
50
|
+
return super()._ex_asin(e)
|
|
51
|
+
|
|
52
|
+
def _ex_atan(self, e):
|
|
53
|
+
warnings.warn('Unsupported function: atan()')
|
|
54
|
+
return super()._ex_atan(e)
|
|
55
|
+
|
|
56
|
+
def _ex_ceil(self, e):
|
|
57
|
+
warnings.warn('Unsupported function: ceil()')
|
|
58
|
+
return super()._ex_ceil(e)
|
|
59
|
+
|
|
60
|
+
# def _ex_cos(self, e):
|
|
61
|
+
# def _ex_derivative(self, e):
|
|
62
|
+
# def _ex_divide(self, e):
|
|
63
|
+
# def _ex_exp(self, e):
|
|
64
|
+
|
|
65
|
+
def _ex_floor(self, e):
|
|
66
|
+
warnings.warn('Unsupported function: floor()')
|
|
67
|
+
return super()._ex_floor(e)
|
|
68
|
+
|
|
69
|
+
# def _ex_log(self, e):
|
|
70
|
+
|
|
71
|
+
def _ex_log10(self, e):
|
|
72
|
+
# Log10(a) = Log(a, 10.0) -> '(log(a) / log(10.0))'
|
|
73
|
+
return super()._ex_log(Log(e[0], Number(10)))
|
|
74
|
+
|
|
75
|
+
# def _ex_minus(self, e):
|
|
76
|
+
# def _ex_multiply(self, e):
|
|
77
|
+
# def _ex_plus(self, e):
|
|
78
|
+
# def _ex_power(self, e):
|
|
79
|
+
# def _ex_prefix_minus(self, e):
|
|
80
|
+
# def _ex_prefix_plus(self, e):
|
|
81
|
+
# def _ex_quotient(self, e):
|
|
82
|
+
# def _ex_remainder(self, e):
|
|
83
|
+
# def _ex_sin(self, e):
|
|
84
|
+
# def _ex_sqrt(self, e):
|
|
85
|
+
# def _ex_tan(self, e):
|
|
86
|
+
|
|
87
|
+
# -- Conditional operators
|
|
88
|
+
|
|
89
|
+
def _ex_and(self, e):
|
|
90
|
+
# (a and b) == a * b, where a, b are in {0, 1}
|
|
91
|
+
return f'{self.ex(e[0])} * {self.ex(e[1])}'
|
|
92
|
+
|
|
93
|
+
def _ex_equal(self, e):
|
|
94
|
+
# (a == b) == heaviside(a - b) * heaviside(b - a)
|
|
95
|
+
return self.ex(And(MoreEqual(e[0], e[1]), LessEqual(e[0], e[1])))
|
|
96
|
+
|
|
97
|
+
def _ex_less(self, e):
|
|
98
|
+
# (a < b) == 1 - heaviside(a - b)
|
|
99
|
+
return self.ex(Not(MoreEqual(e[0], e[1])))
|
|
100
|
+
|
|
101
|
+
def _ex_less_equal(self, e):
|
|
102
|
+
# (a <= b) == heaviside(b - a)
|
|
103
|
+
return f'heaviside({self.ex(e[1])} - {self.ex(e[0])})'
|
|
104
|
+
|
|
105
|
+
def _ex_more(self, e):
|
|
106
|
+
# (a > b) == 1 - heaviside(b - a)
|
|
107
|
+
return self.ex(Not(LessEqual(e[0], e[1])))
|
|
108
|
+
|
|
109
|
+
def _ex_more_equal(self, e):
|
|
110
|
+
# (a >= b) == heaviside(a - b)
|
|
111
|
+
return f'heaviside({self.ex(e[0])} - {self.ex(e[1])})'
|
|
112
|
+
|
|
113
|
+
def _ex_not(self, e):
|
|
114
|
+
# not(a) == (1 - a), where a is in {0, 1}
|
|
115
|
+
return f'(1 - {self.ex(e[0])})'
|
|
116
|
+
|
|
117
|
+
def _ex_not_equal(self, e):
|
|
118
|
+
# (a != b) == 1 - heaviside(a - b) * heaviside(b - a)
|
|
119
|
+
return self.ex(Not(Equal(e[0], e[1])))
|
|
120
|
+
|
|
121
|
+
def _ex_or(self, e):
|
|
122
|
+
# a or b == not(not(a) and not(b)), where a, b are in {0, 1}
|
|
123
|
+
return self.ex(Not(And(Not(e[0]), Not(e[1]))))
|
|
124
|
+
|
|
125
|
+
# -- Conditional expressions
|
|
126
|
+
|
|
127
|
+
def _ex_if(self, e):
|
|
128
|
+
_if = self.ex(e._i)
|
|
129
|
+
_then = self.ex(e._t)
|
|
130
|
+
_not_if = self.ex(Not(e._i))
|
|
131
|
+
_else = self.ex(e._e)
|
|
132
|
+
|
|
133
|
+
return f'({_if} * {_then} + {_not_if} * {_else})'
|
|
134
|
+
|
|
135
|
+
def _ex_piecewise(self, e):
|
|
136
|
+
# Convert piecewise to nested ifs
|
|
137
|
+
# e.g. piecewise(a, b, c, d, e) -> if(a, b, if(c, d, e))
|
|
138
|
+
n = len(e._i)
|
|
139
|
+
|
|
140
|
+
_nested_ifs = e._e[n]
|
|
141
|
+
|
|
142
|
+
for i in range(n - 1, -1, -1):
|
|
143
|
+
_nested_ifs = If(e._i[i], e._e[i], _nested_ifs)
|
|
144
|
+
|
|
145
|
+
return self._ex_if(_nested_ifs)
|