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.
Files changed (59) hide show
  1. myokit/__init__.py +5 -3
  2. myokit/__main__.py +9 -159
  3. myokit/_config.py +2 -2
  4. myokit/_expressions.py +6 -6
  5. myokit/_model_api.py +11 -7
  6. myokit/_myokit_version.py +1 -1
  7. myokit/_protocol.py +4 -0
  8. myokit/_sim/__init__.py +1 -0
  9. myokit/_sim/cvodessim.c +321 -177
  10. myokit/_sim/cvodessim.py +107 -43
  11. myokit/_sim/mcl.h +54 -0
  12. myokit/formats/__init__.py +63 -12
  13. myokit/formats/ansic/__init__.py +2 -1
  14. myokit/formats/ansic/_ewriter.py +159 -40
  15. myokit/formats/cpp/_ewriter.py +12 -1
  16. myokit/formats/cuda/_ewriter.py +15 -51
  17. myokit/formats/easyml/_ewriter.py +26 -54
  18. myokit/formats/heka/_patchmaster.py +15 -3
  19. myokit/formats/latex/_ewriter.py +103 -88
  20. myokit/formats/latex/_exporter.py +1 -1
  21. myokit/formats/mathml/_ewriter.py +2 -2
  22. myokit/formats/matlab/_ewriter.py +50 -28
  23. myokit/formats/opencl/_ewriter.py +61 -78
  24. myokit/formats/python/_ewriter.py +81 -50
  25. myokit/formats/stan/_ewriter.py +29 -37
  26. myokit/gui/source.py +1 -1
  27. myokit/lib/hh.py +3 -0
  28. myokit/lib/markov.py +6 -0
  29. myokit/tests/__init__.py +70 -0
  30. myokit/tests/data/decker.model +59 -59
  31. myokit/tests/test_formats.py +115 -7
  32. myokit/tests/test_formats_ansic.py +344 -0
  33. myokit/tests/test_formats_axon.py +17 -0
  34. myokit/tests/test_formats_cpp.py +97 -0
  35. myokit/tests/test_formats_cuda.py +226 -0
  36. myokit/tests/test_formats_easyml.py +169 -152
  37. myokit/tests/{test_formats_exporters.py → test_formats_exporters_run.py} +1 -69
  38. myokit/tests/test_formats_html.py +1 -3
  39. myokit/tests/test_formats_latex.py +211 -0
  40. myokit/tests/test_formats_mathml_content.py +13 -0
  41. myokit/tests/test_formats_mathml_presentation.py +54 -42
  42. myokit/tests/test_formats_matlab.py +218 -0
  43. myokit/tests/test_formats_opencl.py +206 -380
  44. myokit/tests/test_formats_python.py +557 -0
  45. myokit/tests/test_formats_stan.py +175 -0
  46. myokit/tests/test_formats_sympy.py +9 -2
  47. myokit/tests/test_lib_hh.py +36 -0
  48. myokit/tests/test_lib_plots.py +0 -16
  49. myokit/tests/test_model.py +21 -1
  50. myokit/tests/test_simulation_cvodes.py +137 -56
  51. myokit/tools.py +3 -2
  52. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/LICENSE.txt +1 -1
  53. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/METADATA +19 -8
  54. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/RECORD +57 -52
  55. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/WHEEL +1 -1
  56. myokit/tests/test_formats_expression_writers.py +0 -1281
  57. myokit/tests/test_formats_importers.py +0 -53
  58. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/entry_points.txt +0 -0
  59. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Tests writing of Stan equations.
4
+ #
5
+ # This file is part of Myokit.
6
+ # See http://myokit.org for copyright, sharing, and licensing details.
7
+ #
8
+ import unittest
9
+
10
+ import myokit
11
+ import myokit.formats.latex
12
+
13
+ from myokit import (
14
+ Number, PrefixPlus, PrefixMinus, Plus, Minus,
15
+ Multiply, Divide, Quotient, Remainder, Power, Sqrt,
16
+ Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
17
+ Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
18
+ )
19
+
20
+ import myokit.tests
21
+
22
+
23
+ class LatexExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
24
+ """
25
+ Test conversion of expressions to Python.
26
+ This is used by pyfunc(), although that's usually done with the
27
+ NumPyExpressionWriter instead.
28
+ Numerical tests are provided.
29
+ """
30
+ _name = 'latex'
31
+ _target = myokit.formats.latex.LatexExpressionWriter
32
+ _update_lhs_function = False
33
+
34
+ def test_number(self):
35
+ self.eq(Number(1), '1.0')
36
+ self.eq(Number(-1.3274924373284374), '-1.32749243732843736e+00')
37
+ self.eq(Number(+1.3274924373284374), '1.32749243732843736e+00')
38
+ self.eq(Number(-2), '-2.0')
39
+ self.eq(Number(13, 'mV'), r'13.0 \text{mV}')
40
+ self.eq(Number(2, 'A/F'), r'2.0 \text{A/F}')
41
+ self.eq(Number(7, myokit.units.dimensionless), '7.0')
42
+
43
+ def test_name(self):
44
+ self.eq(self.a, r'\text{a}')
45
+ m = myokit.Model()
46
+ c = m.add_component('c')
47
+ v = c.add_variable('under_score', rhs=3)
48
+ t = c.add_variable('time', rhs=0, binding='time')
49
+ m.validate()
50
+ self.eq(myokit.Name(v), r'\text{under\_score}')
51
+
52
+ w = self._target()
53
+ w.set_lhs_function(lambda v: v.var().qname().upper())
54
+ self.assertEqual(w.ex(self.a), 'COMP.A')
55
+
56
+ def test_derivative(self):
57
+ self.eq(myokit.Derivative(self.a), r'\frac{d\text{a}}{d\text{t}}')
58
+
59
+ w = self._target()
60
+ w.set_time_variable_name('timmy')
61
+ self.assertEqual(w.ex(myokit.Derivative(self.a)),
62
+ r'\frac{d\text{a}}{d\text{timmy}}')
63
+ w.set_time_variable_name('Bob')
64
+ self.assertEqual(w.ex(myokit.Derivative(self.a)),
65
+ r'\frac{d\text{a}}{d\text{Bob}}')
66
+
67
+ def test_partial_derivative(self):
68
+ self.eq(myokit.PartialDerivative(self.a, self.b),
69
+ r'\frac{\partial\text{a}}{\partial\text{b}}')
70
+
71
+ def test_initial_value(self):
72
+ self.eq(myokit.InitialValue(self.a), r'\text{a}(\text{t} = 0)')
73
+
74
+ def test_prefix_plus(self):
75
+ # Test with numbers
76
+ p = Number(11, 'mV')
77
+ self.eq(PrefixPlus(p), r'+11.0 \text{mV}')
78
+ p = Number(3)
79
+ self.eq(PrefixPlus(PrefixPlus(p)), '++3.0')
80
+ self.eq(PrefixPlus(Number('+1')), '+1.0')
81
+
82
+ # Test with operators of precedence SUM, PRODUCT, POWER
83
+ a, b, c = self.abc
84
+ self.eq(PrefixPlus(Plus(a, b)), r'+\left(\text{a}+\text{b}\right)')
85
+ self.eq(Divide(PrefixPlus(Plus(a, b)), c),
86
+ r'\frac{+\left(\text{a}+\text{b}\right)}{\text{c}}')
87
+ self.eq(Power(PrefixPlus(b), a),
88
+ r'\left(+\text{b}\right)^\text{a}')
89
+
90
+ def test_prefix_minus(self):
91
+ # Test with numbers
92
+ p = Number(3, 'uA')
93
+ self.eq(PrefixMinus(p), r'-3.0 \text{uA}')
94
+ p = Number(2)
95
+ self.eq(PrefixMinus(PrefixMinus(p)), '--2.0')
96
+ self.eq(PrefixMinus(Number('-1')), '--1.0')
97
+
98
+ # Test with operators of precedence SUM, PRODUCT, POWER
99
+ a, b, c = self.abc
100
+ self.eq(PrefixMinus(Plus(a, b)), r'-\left(\text{a}+\text{b}\right)')
101
+ self.eq(Divide(PrefixMinus(Plus(a, b)), c),
102
+ r'\frac{-\left(\text{a}+\text{b}\right)}{\text{c}}')
103
+ self.eq(Power(PrefixMinus(b), a),
104
+ r'\left(-\text{b}\right)^\text{a}')
105
+
106
+ def test_plus_minus(self):
107
+ a, b, c = self.abc
108
+ ta, tb, tc = r'\text{a}', r'\text{b}', r'\text{c}'
109
+ self.eq(Plus(a, b), f'{ta}+{tb}')
110
+ self.eq(Plus(Plus(a, b), c), f'{ta}+{tb}+{tc}')
111
+ self.eq(Plus(a, Plus(b, c)), rf'{ta}+\left({tb}+{tc}\right)')
112
+
113
+ self.eq(Minus(a, b), f'{ta}-{tb}')
114
+ self.eq(Minus(Minus(a, b), c), f'{ta}-{tb}-{tc}')
115
+ self.eq(Minus(a, Minus(b, c)), rf'{ta}-\left({tb}-{tc}\right)')
116
+
117
+ self.eq(Minus(a, b), f'{ta}-{tb}')
118
+ self.eq(Plus(Minus(a, b), c), f'{ta}-{tb}+{tc}')
119
+ self.eq(Minus(a, Plus(b, c)), rf'{ta}-\left({tb}+{tc}\right)')
120
+ self.eq(Minus(Plus(a, b), c), f'{ta}+{tb}-{tc}')
121
+ self.eq(Minus(a, Plus(b, c)), rf'{ta}-\left({tb}+{tc}\right)')
122
+
123
+ def test_multiply_divide(self):
124
+ a, b, c = self.abc
125
+ ta, tb, tc = r'\text{a}', r'\text{b}', r'\text{c}'
126
+ l, r = r'\left(', r'\right)'
127
+ self.eq(Multiply(a, b), rf'{ta}\cdot{tb}')
128
+ self.eq(Multiply(Multiply(a, b), c), rf'{ta}\cdot{tb}\cdot{tc}')
129
+ self.eq(Multiply(a, Multiply(b, c)), rf'{ta}\cdot{l}{tb}\cdot{tc}{r}')
130
+ self.eq(Divide(a, b), r'\frac{\text{a}}{\text{b}}')
131
+ self.eq(Divide(Divide(a, b), c),
132
+ r'\frac{\frac{\text{a}}{\text{b}}}{\text{c}}')
133
+ self.eq(Divide(a, Divide(b, c)),
134
+ r'\frac{\text{a}}{\frac{\text{b}}{\text{c}}}')
135
+ self.eq(Divide(Divide(a, b), c),
136
+ r'\frac{\frac{\text{a}}{\text{b}}}{\text{c}}')
137
+ self.eq(Divide(Multiply(a, b), c),
138
+ r'\frac{\text{a}\cdot\text{b}}{\text{c}}')
139
+
140
+ self.eq(Multiply(Minus(a, b), c), rf'{l}{ta}-{tb}{r}\cdot{tc}')
141
+ self.eq(Multiply(a, Plus(b, c)), rf'{ta}\cdot{l}{tb}+{tc}{r}')
142
+ self.eq(Plus(a, Multiply(b, c)), rf'{ta}+{tb}\cdot{tc}')
143
+
144
+ def test_remainder_quotient(self):
145
+ a, b = self.ab
146
+ self.eq(Remainder(a, b), r'\text{a}\bmod\text{b}')
147
+ self.eq(Quotient(a, b),
148
+ r'\left\lfloor\frac{\text{a}}{\text{b}}\right\rfloor')
149
+
150
+ def test_power(self):
151
+ a, b, c = self.abc
152
+ ta, tb, tc = r'\text{a}', r'\text{b}', r'\text{c}'
153
+ l, r = r'\left(', r'\right)'
154
+ p, q = '{', '}'
155
+ self.eq(Power(a, b), f'{ta}^{tb}')
156
+ self.eq(Power(Power(a, b), c), f'{l}{ta}^{tb}{r}^{tc}')
157
+ self.eq(Power(a, Power(b, c)), f'{ta}^{p}{tb}^{tc}{q}')
158
+ self.eq(Power(Plus(a, b), c), f'{l}{ta}+{tb}{r}^{tc}')
159
+ self.eq(Power(a, Minus(b, c)), f'{ta}^{p}{tb}-{tc}{q}')
160
+
161
+ def test_functions(self):
162
+ a, b = self.ab
163
+
164
+ self.eq(Sqrt(a), r'\sqrt{\text{a}}')
165
+ self.eq(Exp(a), r'\exp\left(\text{a}\right)')
166
+ self.eq(Log(a), r'\log\left(\text{a}\right)')
167
+ self.eq(Log(a, b), r'\log_{\text{b}}\left(\text{a}\right)')
168
+ self.eq(Log10(a), r'\log_{10}\left(\text{a}\right)')
169
+ self.eq(Sin(a), r'\sin\left(\text{a}\right)')
170
+ self.eq(Cos(a), r'\cos\left(\text{a}\right)')
171
+ self.eq(Tan(a), r'\tan\left(\text{a}\right)')
172
+ self.eq(ASin(a), r'\arcsin\left(\text{a}\right)')
173
+ self.eq(ACos(a), r'\arccos\left(\text{a}\right)')
174
+ self.eq(ATan(a), r'\arctan\left(\text{a}\right)')
175
+ self.eq(Floor(a), r'\left\lfloor{\text{a}}\right\rfloor')
176
+ self.eq(Ceil(a), r'\left\lceil{\text{a}}\right\rceil')
177
+ self.eq(Abs(a), r'\lvert{\text{a}}\rvert')
178
+
179
+ def test_conditions(self):
180
+ a, b, c, d = self.abcd
181
+
182
+ self.eq(And(a, b), r'\left(\text{a}\and\text{b}\right)')
183
+ self.eq(Or(d, c), r'\left(\text{d}\or\text{c}\right)')
184
+ self.eq(Not(c), r'\left(\not\text{c}\right)')
185
+
186
+ self.eq(Equal(a, b), r'\left(\text{a}=\text{b}\right)')
187
+ self.eq(NotEqual(a, b), r'\left(\text{a}\neq\text{b}\right)')
188
+ self.eq(More(b, a), r'\left(\text{b}>\text{a}\right)')
189
+ self.eq(Less(d, c), r'\left(\text{d}<\text{c}\right)')
190
+ self.eq(MoreEqual(c, a), r'\left(\text{c}\geq\text{a}\right)')
191
+ self.eq(LessEqual(b, d), r'\left(\text{b}\leq\text{d}\right)')
192
+
193
+ self.eq(And(Equal(a, b), NotEqual(c, d)),
194
+ r'\left(\left(\text{a}=\text{b}\right)\and'
195
+ r'\left(\text{c}\neq\text{d}\right)\right)')
196
+ self.eq(Not(Equal(d, d)),
197
+ r'\left(\not\left(\text{d}=\text{d}\right)\right)')
198
+ self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
199
+ r'\left(\left(0.0=0.0\right)=0.0\right)')
200
+
201
+ def test_conditionals(self):
202
+
203
+ a, b, c, d = self.abcd
204
+ self.eq(myokit.If(a, b, c),
205
+ r'\text{if}\left(\text{a},\text{b},\text{c}\right)')
206
+ self.eq(myokit.Piecewise(a, b, c, d, Number(1)), r'\text{piecewise}'
207
+ r'\left(\text{a},\text{b},\text{c},\text{d},1.0\right)')
208
+
209
+
210
+ if __name__ == '__main__':
211
+ unittest.main()
@@ -82,6 +82,10 @@ class ContentMathMLParserTest(unittest.TestCase):
82
82
  e = myokit.PrefixPlus(myokit.Number(1))
83
83
  x = '<apply><plus/><cn>1.0</cn></apply>'
84
84
  self.assertEqual(self.p(x), e)
85
+ e = myokit.PrefixPlus(myokit.Plus(myokit.Number(3), myokit.Number(4)))
86
+ x = ('<apply><plus/><apply><plus /><cn>3.0</cn><cn>4.0</cn></apply>'
87
+ '</apply>')
88
+ self.assertEqual(self.p(x), e)
85
89
 
86
90
  # Prefix minus
87
91
  e = myokit.PrefixMinus(myokit.Number(1))
@@ -358,9 +362,18 @@ class ContentMathMLParserTest(unittest.TestCase):
358
362
  # Power
359
363
  a = myokit.Name('a')
360
364
  b = myokit.Number(1)
365
+ c = myokit.Number(2)
361
366
  e = myokit.Power(a, b)
362
367
  x = '<apply><power/><ci>a</ci><cn>1.0</cn></apply>'
363
368
  self.assertEqual(self.p(x), e)
369
+ e = myokit.Power(myokit.Power(a, b), c)
370
+ x = ('<apply><power/><apply><power/><ci>a</ci><cn>1.0</cn></apply>'
371
+ '<cn>2.0</cn></apply>')
372
+ self.assertEqual(self.p(x), e)
373
+ e = myokit.Power(a, myokit.Power(b, c))
374
+ x = ('<apply><power/><ci>a</ci><apply><power/><cn>1.0</cn><cn>2.0</cn>'
375
+ '</apply></apply>')
376
+ self.assertEqual(self.p(x), e)
364
377
 
365
378
  #TODO: Degree etc.
366
379
 
@@ -22,6 +22,7 @@ class PresentationMathMLTest(unittest.TestCase):
22
22
  model = myokit.Model()
23
23
  component = model.add_component('c')
24
24
  cls.avar = component.add_variable('a')
25
+ cls.bvar = component.add_variable('b')
25
26
 
26
27
  cls.w = mathml.MathMLExpressionWriter()
27
28
  cls.w.set_mode(presentation=True)
@@ -46,33 +47,40 @@ class PresentationMathMLTest(unittest.TestCase):
46
47
 
47
48
  # Plus
48
49
  x = myokit.Plus(a, b)
49
- self.assertWrite(x, '<mrow>' + ca + '<mo>+</mo>' + cb + '</mrow>')
50
+ self.assertWrite(x, f'<mrow>{ca}<mo>+</mo>{cb}</mrow>')
50
51
 
51
52
  # Minus
52
53
  x = myokit.Minus(a, b)
53
- self.assertWrite(x, '<mrow>' + ca + '<mo>-</mo>' + cb + '</mrow>')
54
+ self.assertWrite(x, f'<mrow>{ca}<mo>-</mo>{cb}</mrow>')
54
55
 
55
56
  # Multiply
56
57
  x = myokit.Multiply(a, b)
57
- self.assertWrite(x, '<mrow>' + ca + '<mo>*</mo>' + cb + '</mrow>')
58
+ self.assertWrite(x, f'<mrow>{ca}<mo>*</mo>{cb}</mrow>')
58
59
 
59
60
  # Divide
60
61
  x = myokit.Divide(a, b)
61
- self.assertWrite(x, '<mfrac>' + ca + cb + '</mfrac>')
62
+ self.assertWrite(x, f'<mfrac>{ca + cb}</mfrac>')
62
63
 
63
64
  def test_arithmetic_unary(self):
64
65
  # Tests writing prefix operators
65
66
 
67
+ a = myokit.Name(self.avar)
66
68
  b = myokit.Number('12', 'pF')
69
+ ca = '<mi>c.a</mi>'
67
70
  cb = '<mn>12.0</mn>'
68
71
 
69
72
  # Prefix plus
70
73
  x = myokit.PrefixPlus(b)
71
- self.assertWrite(x, '<mrow><mo>+</mo>' + cb + '</mrow>')
74
+ self.assertWrite(x, f'<mrow><mo>+</mo>{cb}</mrow>')
75
+ x = myokit.Divide(myokit.PrefixPlus(myokit.Plus(a, b)), a)
76
+ self.assertWrite(
77
+ x,
78
+ f'<mfrac><mrow><mo>+</mo><mo>(</mo><mrow>{ca}<mo>+</mo>{cb}</mrow>'
79
+ f'<mo>)</mo></mrow>{ca}</mfrac>')
72
80
 
73
81
  # Prefix minus
74
82
  x = myokit.PrefixMinus(b)
75
- self.assertWrite(x, '<mrow><mo>-</mo>' + cb + '</mrow>')
83
+ self.assertWrite(x, f'<mrow><mo>-</mo>{cb}</mrow>')
76
84
 
77
85
  def test_conditionals(self):
78
86
  # Tests if and piecewise writing
@@ -94,8 +102,8 @@ class PresentationMathMLTest(unittest.TestCase):
94
102
  self.assertWrite(
95
103
  x,
96
104
  '<piecewise>'
97
- '<piece>' + ca + c1 + '</piece>'
98
- '<otherwise>' + cb + '</otherwise>'
105
+ f'<piece>{ca + c1}</piece>'
106
+ f'<otherwise>{cb}</otherwise>'
99
107
  '</piecewise>'
100
108
  )
101
109
  # Piecewise
@@ -103,9 +111,9 @@ class PresentationMathMLTest(unittest.TestCase):
103
111
  self.assertWrite(
104
112
  x,
105
113
  '<piecewise>'
106
- '<piece>' + ca + c1 + '</piece>'
107
- '<piece>' + cb + c2 + '</piece>'
108
- '<otherwise>' + cc + '</otherwise>'
114
+ f'<piece>{ca + c1}</piece>'
115
+ f'<piece>{cb + c2}</piece>'
116
+ f'<otherwise>{cc}</otherwise>'
109
117
  '</piecewise>'
110
118
  )
111
119
 
@@ -119,63 +127,69 @@ class PresentationMathMLTest(unittest.TestCase):
119
127
  # Tests writing basic functions
120
128
 
121
129
  a = myokit.Name(self.avar)
122
- b = myokit.Number('12', 'pF')
130
+ b = myokit.Name(self.bvar)
131
+ c = myokit.Number('12', 'pF')
123
132
  ca = '<mi>c.a</mi>'
124
- cb = '<mn>12.0</mn>'
133
+ cb = '<mi>c.b</mi>'
134
+ cc = '<mn>12.0</mn>'
125
135
 
126
136
  # Power
127
137
  x = myokit.Power(a, b)
128
- self.assertWrite(x, '<msup>' + ca + cb + '</msup>')
138
+ self.assertWrite(x, f'<msup>{ca}{cb}</msup>')
139
+ x = myokit.Power(myokit.Power(a, b), c)
140
+ self.assertWrite(x, f'<msup><msup>{ca}{cb}</msup>{cc}</msup>')
141
+ x = myokit.Power(a, myokit.Power(b, c))
142
+ self.assertWrite(x, f'<msup>{ca}<msup>{cb}{cc}</msup></msup>')
129
143
 
130
144
  # Sqrt
131
145
  x = myokit.Sqrt(b)
132
146
  self.assertWrite(
133
- x, '<mrow><mi>root</mi><mfenced>' + cb + '</mfenced></mrow>')
147
+ x, f'<mrow><mi>root</mi><mfenced>{cb}</mfenced></mrow>')
134
148
 
135
149
  # Exp
136
150
  x = myokit.Exp(a)
137
- self.assertWrite(x, '<msup><mi>e</mi>' + ca + '</msup>')
151
+ self.assertWrite(x, f'<msup><mi>e</mi>{ca}</msup>')
138
152
 
139
153
  # Log(a)
140
154
  x = myokit.Log(b)
141
155
  self.assertWrite(
142
- x, '<mrow><mi>ln</mi><mfenced>' + cb + '</mfenced></mrow>')
156
+ x, f'<mrow><mi>ln</mi><mfenced>{cb}</mfenced></mrow>')
143
157
 
144
158
  # Log(a, b)
145
159
  x = myokit.Log(a, b)
146
160
  self.assertWrite(
147
161
  x,
148
- '<mrow><msub><mi>log</mi>' + cb + '</msub>'
149
- '<mfenced>' + ca + '</mfenced></mrow>'
162
+ f'<mrow><msub><mi>log</mi>{cb}</msub>'
163
+ f'<mfenced>{ca}</mfenced></mrow>'
150
164
  )
151
165
 
152
166
  # Log10
153
167
  x = myokit.Log10(b)
154
168
  self.assertWrite(
155
- x, '<mrow><mi>log</mi><mfenced>' + cb + '</mfenced></mrow>')
169
+ x, f'<mrow><mi>log</mi><mfenced>{cb}</mfenced></mrow>')
156
170
 
157
171
  # Floor
158
172
  x = myokit.Floor(b)
159
173
  self.assertWrite(
160
- x, '<mrow><mi>floor</mi><mfenced>' + cb + '</mfenced></mrow>')
174
+ x, f'<mrow><mi>floor</mi><mfenced>{cb}</mfenced></mrow>')
161
175
 
162
176
  # Ceil
163
177
  x = myokit.Ceil(b)
164
178
  self.assertWrite(
165
- x, '<mrow><mi>ceiling</mi><mfenced>' + cb + '</mfenced></mrow>')
179
+ x, f'<mrow><mi>ceiling</mi><mfenced>{cb}</mfenced></mrow>')
166
180
 
167
181
  # Abs
168
182
  x = myokit.Abs(b)
169
183
  self.assertWrite(
170
- x, '<mrow><mi>abs</mi><mfenced>' + cb + '</mfenced></mrow>')
184
+ x, f'<mrow><mi>abs</mi><mfenced>{cb}</mfenced></mrow>')
171
185
 
172
186
  # Quotient
173
187
  x = myokit.Quotient(a, b)
174
- self.assertWrite(x, '<mrow>' + ca + '<mo>//</mo>' + cb + '</mrow>')
188
+ self.assertWrite(x, f'<mrow>{ca}<mo>//</mo>{cb}</mrow>')
175
189
 
176
190
  # Remainder
177
191
  x = myokit.Remainder(a, b)
178
- self.assertWrite(x, '<mrow>' + ca + '<mo>%</mo>' + cb + '</mrow>')
192
+ self.assertWrite(x, f'<mrow>{ca}<mo>%</mo>{cb}</mrow>')
179
193
 
180
194
  def test_inequalities(self):
181
195
  # Test writing (in)equalities
@@ -187,31 +201,29 @@ class PresentationMathMLTest(unittest.TestCase):
187
201
 
188
202
  # Equal
189
203
  x = myokit.Equal(a, b)
190
- self.assertWrite(x, '<mrow>' + ca + '<mo>==</mo>' + cb + '</mrow>')
204
+ self.assertWrite(x, f'<mrow>{ca}<mo>==</mo>{cb}</mrow>')
191
205
 
192
206
  # NotEqual
193
207
  x = myokit.NotEqual(a, b)
194
- self.assertWrite(x, '<mrow>' + ca + '<mo>!=</mo>' + cb + '</mrow>')
208
+ self.assertWrite(x, f'<mrow>{ca}<mo>!=</mo>{cb}</mrow>')
195
209
 
196
210
  # More
197
211
  x = myokit.More(a, b)
198
- self.assertWrite(x, '<mrow>' + ca + '<mo>&gt;</mo>' + cb + '</mrow>')
212
+ self.assertWrite(x, f'<mrow>{ca}<mo>&gt;</mo>{cb}</mrow>')
199
213
 
200
214
  # Less
201
215
  x = myokit.Less(a, b)
202
- self.assertWrite(x, '<mrow>' + ca + '<mo>&lt;</mo>' + cb + '</mrow>')
216
+ self.assertWrite(x, f'<mrow>{ca}<mo>&lt;</mo>{cb}</mrow>')
203
217
 
204
218
  # MoreEqual
205
219
  # Named version &ge; is not output, shows decimal code instead
206
220
  x = myokit.MoreEqual(a, b)
207
- self.assertWrite(
208
- x, '<mrow>' + ca + '<mo>&#8805;</mo>' + cb + '</mrow>')
221
+ self.assertWrite(x, f'<mrow>{ca}<mo>&#8805;</mo>{cb}</mrow>')
209
222
 
210
223
  # LessEqual
211
224
  # Named version &le; is not output, shows decimal code instead
212
225
  x = myokit.LessEqual(a, b)
213
- self.assertWrite(
214
- x, '<mrow>' + ca + '<mo>&#8804;</mo>' + cb + '</mrow>')
226
+ self.assertWrite(x, f'<mrow>{ca}<mo>&#8804;</mo>{cb}</mrow>')
215
227
 
216
228
  def test_logic_operators(self):
217
229
  # Tests writing logic operators
@@ -224,15 +236,15 @@ class PresentationMathMLTest(unittest.TestCase):
224
236
  # Not
225
237
  x = myokit.Not(cond1)
226
238
  self.assertWrite(
227
- x, '<mrow><mo>(</mo><mo>not</mo>' + c1 + '<mo>)</mo></mrow>')
239
+ x, f'<mrow><mo>not</mo><mo>(</mo>{c1}<mo>)</mo></mrow>')
228
240
 
229
241
  # And
230
242
  x = myokit.And(cond1, cond2)
231
- self.assertWrite(x, '<mrow>' + c1 + '<mo>and</mo>' + c2 + '</mrow>')
243
+ self.assertWrite(x, f'<mrow>{c1}<mo>and</mo>{c2}</mrow>')
232
244
 
233
245
  # Or
234
246
  x = myokit.Or(cond1, cond2)
235
- self.assertWrite(x, '<mrow>' + c1 + '<mo>or</mo>' + c2 + '</mrow>')
247
+ self.assertWrite(x, f'<mrow>{c1}<mo>or</mo>{c2}</mrow>')
236
248
 
237
249
  def test_name_and_numbers(self):
238
250
  # Test name and number writing
@@ -259,32 +271,32 @@ class PresentationMathMLTest(unittest.TestCase):
259
271
  # Sin
260
272
  x = myokit.Sin(b)
261
273
  self.assertWrite(
262
- x, '<mrow><mi>sin</mi><mfenced>' + cb + '</mfenced></mrow>')
274
+ x, f'<mrow><mi>sin</mi><mfenced>{cb}</mfenced></mrow>')
263
275
 
264
276
  # Cos
265
277
  x = myokit.Cos(b)
266
278
  self.assertWrite(
267
- x, '<mrow><mi>cos</mi><mfenced>' + cb + '</mfenced></mrow>')
279
+ x, f'<mrow><mi>cos</mi><mfenced>{cb}</mfenced></mrow>')
268
280
 
269
281
  # Tan
270
282
  x = myokit.Tan(b)
271
283
  self.assertWrite(
272
- x, '<mrow><mi>tan</mi><mfenced>' + cb + '</mfenced></mrow>')
284
+ x, f'<mrow><mi>tan</mi><mfenced>{cb}</mfenced></mrow>')
273
285
 
274
286
  # ASin
275
287
  x = myokit.ASin(b)
276
288
  self.assertWrite(
277
- x, '<mrow><mi>arcsin</mi><mfenced>' + cb + '</mfenced></mrow>')
289
+ x, f'<mrow><mi>arcsin</mi><mfenced>{cb}</mfenced></mrow>')
278
290
 
279
291
  # ACos
280
292
  x = myokit.ACos(b)
281
293
  self.assertWrite(
282
- x, '<mrow><mi>arccos</mi><mfenced>' + cb + '</mfenced></mrow>')
294
+ x, f'<mrow><mi>arccos</mi><mfenced>{cb}</mfenced></mrow>')
283
295
 
284
296
  # ATan
285
297
  x = myokit.ATan(b)
286
298
  self.assertWrite(
287
- x, '<mrow><mi>arctan</mi><mfenced>' + cb + '</mfenced></mrow>')
299
+ x, f'<mrow><mi>arctan</mi><mfenced>{cb}</mfenced></mrow>')
288
300
 
289
301
  def test_unknown_expression(self):
290
302
  # Test without a Myokit expression