Repository: myokit/myokit (language: python)

Issue:
Bug with PrefixPlus operator in code export and Simulation
```
#!/usr/bin/env python
import myokit

x = myokit.parse_expression('(b - c) / d')
y = myokit.parse_expression('+(b - c) / d')
e = myokit.formats.ewriter('python')
print(e.ex(x))
print(e.ex(y))
```
results in
```
(str:b - str:c) / str:d
str:b - str:c / str:d
```


(To be clear, it's only when you use the _prefix_ plus operator, so "+1 / 2" that it comes into play. "3 + 4" works as expected)


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c267e593c..0fd0b7b4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,10 @@ This page lists the main changes made to Myokit in each release.
 ## Unreleased
 - Added
   - [#1051](https://github.com/myokit/myokit/pull/1051) Added a method `Simulation.crash_inputs` that returns the inputs (binding values) if the last run raised a `SimulationError`.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) Added an abstract class `CBasedExpressionWriter` to use as base class for expression writers for C-like languages.
 - Changed
   - [#1051](https://github.com/myokit/myokit/pull/1051) `Model.evaluate_derivatives` will now use `numpy.errstate(all='raise')` when `ignore_errors` is set to `False`, causing more numerical issues to be reported.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) `Expression.pyfunc` now sorts its arguments in alphabetical order.
 - Deprecated
   - [#1051](https://github.com/myokit/myokit/pull/1051) `Simulation.eval_derivatives` is deprecated in favour of an `evaluate_derivatives` method that uses `default_state` instead of `state` when no state is given, and has better support and clearer rules for handling bindings.
   - [#1051](https://github.com/myokit/myokit/pull/1051) `Simulation.last_state` is deprecated in favour of `crash_state`.
@@ -16,6 +18,11 @@ This page lists the main changes made to Myokit in each release.
   - [#1050](https://github.com/myokit/myokit/pull/1050) Fixed warnings about unknown escape sequences.
   - [#1051](https://github.com/myokit/myokit/pull/1051) Numerical simulation errors in `Simulation` now re-evaluate the state using the last inputs, as well as the last state.
   - [#1051](https://github.com/myokit/myokit/pull/1051) The `Simulation` class now provides error states and inputs for both cvode-detected crashes and too many zero-length step crashes.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) `ExpressionWriter` classes now always output a `PrefixPlus`, including brackets where necessary.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) The `AnsiCExpressionWriter` and related expression writers now wrap all conditions in brackets.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) The `PythonExpressionWriter`, `NumPyExpressionWriter`, `StanExpressionWriter`, and `LatexExpressionWriter` now output brackets around double `Power` operations, so that the Myokit expression `a**b**c` is interpreted as `(a**b)**c`.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) Fixed bracket adding for `PrefixPlus`, `PrefixMinus`, and `Not` in `LatexExpressionWriter`.
+  - [#1055](https://github.com/myokit/myokit/pull/1055) Fixed bracket adding for `PrefixPlus` and `PrefixMinus` in Presentation MathML.
 
 ## [1.35.4] - 2023-10-12
 - Added
diff --git a/docs/source/api_formats/c.rst b/docs/source/api_formats/c.rst
index f86401472..bb9eed999 100644
--- a/docs/source/api_formats/c.rst
+++ b/docs/source/api_formats/c.rst
@@ -33,3 +33,6 @@ provided:
 
 .. autoclass:: AnsiCExpressionWriter
     :inherited-members:
+
+.. autoclass:: CBasedExpressionWriter
+    :inherited-members:
diff --git a/docs/source/api_index/myokit.formats.rst b/docs/source/api_index/myokit.formats.rst
index 93818ac5e..208b48885 100644
--- a/docs/source/api_index/myokit.formats.rst
+++ b/docs/source/api_index/myokit.formats.rst
@@ -24,6 +24,7 @@ myokit.formats.ansic
 - :class:`myokit.formats.ansic.AnsiCCableExporter`
 - :class:`myokit.formats.ansic.AnsiCEulerExporter`
 - :class:`myokit.formats.ansic.AnsiCExpressionWriter`
+- :class:`myokit.formats.ansic.CBasedExpressionWriter`
 - :meth:`myokit.formats.ansic.exporters`
 - :meth:`myokit.formats.ansic.ewriters`
 
diff --git a/myokit/__main__.py b/myokit/__main__.py
index 3573ae325..fe5f4209d 100644
--- a/myokit/__main__.py
+++ b/myokit/__main__.py
@@ -1239,11 +1239,6 @@ def add_test_parser(subparsers):
         help='Test documentation cover, building, and doc tests.')
     doc_parser.set_defaults(testfunc=test_documentation)
 
-    # Example notebooks
-    example_parser = subparsers.add_parser(
-        'examples', help='Test example notebooks.')
-    example_parser.set_defaults(testfunc=test_examples)
-
     # Style tests
     style_parser = subparsers.add_parser('style', help='Run code style tests.')
     style_parser.set_defaults(testfunc=test_style)
@@ -1643,151 +1638,6 @@ def read_file(fpath, classes, functions):
     return n == 0
 
 
-def test_examples(args):
-    """
-    Tests the example notebooks.
-    """
-    books = test_examples_list('examples')
-    print(books)
-
-    print('Found ' + str(len(books)) + ' notebook(s).')
-    test_examples_index('examples', books)
-    test_examples_all('examples', books)
-
-
-def test_examples_index(root, books):
-    """ Check that every notebook is included in the index. """
-    import os
-    import sys
-
-    print('Checking index...')
-
-    # Index file is in ./examples/README.md
-    index_file = os.path.join(root, 'README.md')
-    with open(index_file, 'r') as f:
-        index_contents = f.read()
-
-    # Find which are not indexed
-    not_indexed = [book for book in books if book not in index_contents]
-
-    # Report any failures
-    if len(not_indexed) > 0:
-        print('FAIL: Unindexed notebooks')
-        for book in sorted(not_indexed):
-            print('  ' + str(book))
-        sys.exit(1)
-    else:
-        print('ok: All (' + str(len(books)) + ') notebooks are indexed.')
-
-
-def test_examples_list(root, recursive=True):
-    """ Returns a list of all notebooks in a directory. """
-    import os
-
-    def scan(root, recursive, notebooks):
-        for filename in os.listdir(root):
-            path = os.path.join(root, filename)
-
-            # Add notebook
-            if os.path.splitext(path)[1] == '.ipynb':
-                notebooks.append(path)
-
-            # Recurse into subdirectories
-            elif recursive and os.path.isdir(path):
-                # Ignore hidden directories
-                if filename[:1] == '.':
-                    continue
-                scan(path, recursive, notebooks)
-        return notebooks
-
-    notebooks = []
-    scan(root, recursive, notebooks)
-    notebooks = [os.path.relpath(book, root) for book in notebooks]
-
-    return notebooks
-
-
-def test_examples_single(root, path):
-    """ Tests a notebook in a subprocess, exists if it doesn't finish. """
-    import myokit
-    import nbconvert
-    import os
-    import subprocess
-    import sys
-
-    b = myokit.tools.Benchmarker()
-    print('Running ' + path + ' ... ', end='')
-    sys.stdout.flush()
-
-    # Load notebook, convert to python
-    e = nbconvert.exporters.PythonExporter()
-    code, _ = e.from_filename(os.path.join(root, path))
-
-    # Remove coding statement, if present
-    code = '\n'.join([x for x in code.splitlines() if x[:9] != '# coding'])
-
-    # Tell matplotlib not to produce any figures
-    env = os.environ.copy()
-    env['MPLBACKEND'] = 'Template'
-
-    # Run in subprocess
-    cmd = [sys.executable, '-c', code]
-    curdir = os.getcwd()
-    try:
-        os.chdir(root)
-        p = subprocess.Popen(
-            cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
-        )
-        stdout, stderr = p.communicate(timeout=3600)
-        if p.returncode != 0:
-            # Show failing code, output and errors before returning
-            print('ERROR')
-            print('-- script ' + '-' * (79 - 10))
-            for i, line in enumerate(code.splitlines()):
-                j = str(1 + i)
-                print(j + ' ' * (5 - len(j)) + line)
-            print('-- stdout ' + '-' * (79 - 10))
-            print(stdout)
-            print('-- stderr ' + '-' * (79 - 10))
-            print(stderr)
-            print('-' * 79)
-            return False
-    except KeyboardInterrupt:
-        p.terminate()
-        print('ABORTED')
-        sys.exit(1)
-    finally:
-        os.chdir(curdir)
-
-    # Successfully run
-    print('ok (' + b.format(b.time()) + ')')
-    return True
-
-
-def test_examples_all(root, books):
-    """ Runs all notebooks, and exits if one fails. """
-    import sys
-
-    # Ignore books with deliberate errors, but check they still exist
-    ignore_list = [
-    ]
-    books = set(books) - set(ignore_list)
-
-    # Scan and run
-    print('Testing notebooks')
-    failed = []
-    for book in books:
-        if not test_examples_single(root, book):
-            failed.append(book)
-    if failed:
-        print('FAIL: Errors encountered in notebooks')
-        for book in failed:
-            print('  ' + str(book))
-        sys.exit(1)
-    else:
-        print('ok: Successfully ran all (' + str(len(books)) + ') notebooks.')
-
-
 def test_examples_web(args):
     """
     Runs all web examples, exits if one of them fails.
diff --git a/myokit/_expressions.py b/myokit/_expressions.py
index edb99fd2f..c3c9a4bf6 100644
--- a/myokit/_expressions.py
+++ b/myokit/_expressions.py
@@ -665,8 +665,8 @@ def pyfunc(self, use_numpy=True):
 
         # Create function text
         args = [w.ex(x) for x in self._references]
-        c = 'def ex_pyfunc_generated(' + ','.join(args) + '):\n    return ' \
-            + w.ex(self)
+        c = ('def ex_pyfunc_generated(' + ','.join(sorted(args))
+             + '):\n    return ' + w.ex(self))
 
         # Create function
         local = {}
@@ -682,8 +682,8 @@ def pystr(self, use_numpy=False):
         """
         Returns a string representing this expression in Python syntax.
 
-        By default, built-in functions such as 'exp' are converted to the
-        Python version 'math.exp'. To use the numpy versions, set
+        By default, built-in functions such as ``exp`` are converted to the
+        Python version ``math.exp``. To use the numpy versions, set
         ``numpy=True``.
         """
         # Get expression writer
@@ -810,7 +810,6 @@ def walker(op):
 class Number(Expression):
     """
     Represents a number with an optional unit for use in Myokit expressions.
-    All numbers used in Myokit expressions are floating point.
 
     >>> import myokit
     >>> x = myokit.Number(10)
@@ -821,11 +820,12 @@ class Number(Expression):
     >>> print(x)
     5 [V]
 
+    All numbers used in Myokit expressions are floating point.
+
     Arguments:
 
     ``value``
         A numerical value (something that can be converted to a ``float``).
-        Number objects are immutable so no clone constructor is provided.
     ``unit``
         A unit to associate with this number. If no unit is specified the
         number's unit will be left undefined.
diff --git a/myokit/formats/__init__.py b/myokit/formats/__init__.py
index 34eec8a09..ed8e6cc4b 100644
--- a/myokit/formats/__init__.py
+++ b/myokit/formats/__init__.py
@@ -24,6 +24,9 @@
 class Exporter:
     """
     Abstract base class for exporters.
+
+    An exporter is a class that can produce model code or runnable code from a
+    Myokit model or a model & protocol combination (runnable).
     """
     def __init__(self):
         super().__init__()
@@ -106,15 +109,46 @@ def exporters():
 
 class ExpressionWriter:
     """
-    Base class for expression writers, that take myokit expressions as input
-    and convert them to text or other formats.
+    Base class for expression writers.
+
+    Expression writers take :class:`myokit.Expression` objects and produce code
+    in some language other than Myokit.
+
+    When implementing an expression writer, there are a few important edge
+    cases to consider:
+
+    1. Simplifications must not be made at this stage. For example, if the user
+       writes "+1" instead of "1", the code should output "+1". If
+       simplifications are desired, these should be made at an earlier stage,
+       when everything is still in symbolic form.
+
+    2. Powers (exponentiation) can be left or right-associative. In Myokit and,
+       for example, in Matlab, the expression ``a^b^c`` is interpreted as
+       ``(a^b)^c``. By in Python (and e.g. in many spreadsheet applications)
+       ``a**b**c`` is interpreted as ``a**(b**c)``, necessitating a different
+       bracket-adding logic than used in Myokit.
+
+    3. Binary operators are sometimes implemented as n-ary operators. In
+       Myokit, ``0 == 0 == 0`` is a sequence of two binary operators,
+       interpreted as ``(0 == 0) == 0``. Because ``(0 == 0)`` evaluates to
+       ``1``, this expression returns ``0`` (1 does not equal 0). In Python,
+       the expression ``0 == 0 == 0`` is a ternary (n-ary) operator,
+       interpreted as ``all_equal(0, 0, 0)``, which evaluates to ``1``. For
+       languages that use this convention, extra brackets must be added.
+
+    4. Myokit does not have increment or decrement operators ``--`` and ``++``,
+       so the expression ``--x`` is interpreted as ``-(-x)``. This is the same
+       in Python. But in C-based languages, this is interpreted as a decrement
+       operator so care must be taken to add extra brackets.
+
     """
     def __init__(self):
         self._op_map = self._build_op_map()
 
     def _build_op_map(self):
         """
-        Returns a mapping from myokit operators to lambda functions on expr.
+        Returns a mapping from Myokit operators to lambda functions on
+        expressions.
         """
         return {
             myokit.Name: self._ex_name,
@@ -158,15 +192,11 @@ def _build_op_map(self):
         }
 
     def eq(self, q):
-        """
-        Converts an equation to a string
-        """
+        """ Converts a :class:`myokit.Equation` to a string. """
         return self.ex(q.lhs) + ' = ' + self.ex(q.rhs)
 
     def ex(self, e):
-        """
-        Converts an Expression to a string.
-        """
+        """ Converts a :class:`myokit.Expression` to a string. """
         t = type(e)
         if t not in self._op_map:   # pragma: no cover
             raise ValueError('Unknown expression type: ' + str(t))
@@ -188,9 +218,17 @@ def _ex_number(self, e):
         raise NotImplementedError
 
     def _ex_prefix_plus(self, e):
+        #
+        # Note: Spaces or brackets should be used in languages where ++ is an
+        #       operator
+        #
         raise NotImplementedError
 
     def _ex_prefix_minus(self, e):
+        #
+        # Note: Spaces or brackets should be used in languages where -- is an
+        #       operator
+        #
         raise NotImplementedError
 
     def _ex_plus(self, e):
@@ -212,6 +250,10 @@ def _ex_remainder(self, e):
         raise NotImplementedError
 
     def _ex_power(self, e):
+        #
+        # Note: Most languages agree that -a**b means -(a**b)
+        #       https://codeplea.com/exponentiation-associativity-options
+        #
         raise NotImplementedError
 
     def _ex_sqrt(self, e):
@@ -239,6 +281,11 @@ def _ex_exp(self, e):
         raise NotImplementedError
 
     def _ex_log(self, e):
+        #
+        # Log can have 1 or 2 arguments. If the language does not support
+        # 2-argument log, use (log(a) / log(b)), including the outer
+        # brackets (because a function is expected, which is never bracketed).
+        #
         raise NotImplementedError
 
     def _ex_log10(self, e):
@@ -253,10 +300,11 @@ def _ex_ceil(self, e):
     def _ex_abs(self, e):
         raise NotImplementedError
 
-    def _ex_not(self, e):
-        raise NotImplementedError
-
     def _ex_equal(self, e):
+        #
+        # To obtain the desired `(0 == 0) == 0` behaviour, this and other
+        # conditions are best rendered with brackets.
+        #
         raise NotImplementedError
 
     def _ex_not_equal(self, e):
@@ -280,6 +328,9 @@ def _ex_and(self, e):
     def _ex_or(self, e):
         raise NotImplementedError
 
+    def _ex_not(self, e):
+        raise NotImplementedError
+
     def _ex_if(self, e):
         raise NotImplementedError
 
diff --git a/myokit/formats/ansic/__init__.py b/myokit/formats/ansic/__init__.py
index aac6252eb..02c300263 100644
--- a/myokit/formats/ansic/__init__.py
+++ b/myokit/formats/ansic/__init__.py
@@ -5,7 +5,7 @@
 # See http://myokit.org for copyright, sharing, and licensing details.
 #
 from ._exporter import AnsiCExporter, AnsiCCableExporter, AnsiCEulerExporter
-from ._ewriter import AnsiCExpressionWriter
+from ._ewriter import AnsiCExpressionWriter, CBasedExpressionWriter  # noqa
 
 
 # Importers
@@ -28,6 +28,7 @@ def exporters():
 # Expression writers
 _ewriters = {
     'ansic': AnsiCExpressionWriter,
+    # c-based is intended as base class only
 }
 
 
diff --git a/myokit/formats/ansic/_ewriter.py b/myokit/formats/ansic/_ewriter.py
index 8e6669edc..3fae5dfa5 100644
--- a/myokit/formats/ansic/_ewriter.py
+++ b/myokit/formats/ansic/_ewriter.py
@@ -9,32 +9,38 @@
 from myokit.formats.python import PythonExpressionWriter
 
 
-class AnsiCExpressionWriter(PythonExpressionWriter):
+class CBasedExpressionWriter(PythonExpressionWriter):
     """
-    This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` writes
-    equations for variables in a C-style syntax.
+    Base class for C-style expression writers.
     """
     def __init__(self):
         super().__init__()
         self._function_prefix = ''
-        self._fcond = None
 
-    def set_condition_function(self, func=None):
-        """
-        Sets a function name to use for if statements
+    def _ex_prefix(self, e, op):
+        # PrefixPlus and PrefixMinus. No simplifications should be made here
+        # for PrefixPlus, see https://github.com/myokit/myokit/issues/1054
+        x = self.ex(e[0])
+        return f'{op}({x})' if e.bracket(e[0]) or x[0] == op else f'{op}{x}'
 
-        By setting func to None you can revert back to the default behavior
-         (the ternary operator). Any other value will be interpreted as the
-         name of a C function taking arguments (condition, value_if_true,
-         value_if_false).
-        """
-        self._fcond = func
+    def _ex_infix_comparison(self, e, op):
+        # For equals etc
+        return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
+
+    def _ex_infix_logical(self, e, op):
+        # For and and or
+        return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
 
     #def _ex_name(self, e):
     #def _ex_derivative(self, e):
+    #def _ex_initial_value(self, e):
+    #def _ex_partial_derivative(self, e):
+
     #def _ex_number(self, e):
+
     #def _ex_prefix_plus(self, e):
     #def _ex_prefix_minus(self, e):
+
     #def _ex_plus(self, e):
     #def _ex_minus(self, e):
     #def _ex_multiply(self, e):
@@ -43,17 +49,20 @@ def set_condition_function(self, func=None):
     def _ex_quotient(self, e):
         # Note that this _must_ round towards minus infinity!
         # See myokit.Quotient !
+        # No extra brackets needed: Function
         return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
 
     def _ex_remainder(self, e):
         # Note that this _must_ use the same round-to-neg-inf convention as
         # myokit.Quotient! Implementation below is consistent with Python
-        # convention:
-        return self.ex(myokit.Minus(
-            e[0], myokit.Multiply(e[1], myokit.Quotient(e[0], e[1]))))
+        # convention.
+        # Extra brackets needed! Minus has lower precedence than division.
+        i = myokit.Minus(e[0], myokit.Multiply(
+            e[1], myokit.Floor(myokit.Divide(e[0], e[1]))))
+        return f'({self.ex(i)})'
 
     def _ex_power(self, e):
-        return 'pow(' + self.ex(e[0]) + ', ' + self.ex(e[1]) + ')'
+        return f'pow({self.ex(e[0])}, {self.ex(e[1])})'
 
     #def _ex_sqrt(self, e):
     #def _ex_sin(self, e):
@@ -67,7 +76,9 @@ def _ex_power(self, e):
     def _ex_log(self, e):
         if len(e) == 1:
             return self._ex_function(e, 'log')
-        return '(log(' + self.ex(e[0]) + ') / log(' + self.ex(e[1]) + '))'
+        # Always add brackets: Parent was expecting a function so will never
+        # have added them.
+        return f'(log({self.ex(e[0])}) / log({self.ex(e[1])}))'
 
     #def _ex_log10(self, e):
     #def _ex_floor(self, e):
@@ -76,9 +87,6 @@ def _ex_log(self, e):
     def _ex_abs(self, e):
         return self._ex_function(e, 'fabs')
 
-    def _ex_not(self, e):
-        return '!(' + self.ex(e[0]) + ')'
-
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
     #def _ex_more(self, e):
@@ -87,32 +95,143 @@ def _ex_not(self, e):
     #def _ex_less_equal(self, e):
 
     def _ex_and(self, e):
-        return self._ex_infix_condition(e, '&&')
+        return self._ex_infix_logical(e, '&&')
 
     def _ex_or(self, e):
-        return self._ex_infix_condition(e, '||')
+        return self._ex_infix_logical(e, '||')
+
+    def _ex_not(self, e):
+        # C conditions all have brackets, so don't add more
+        if isinstance(e[0], (myokit.Condition)):
+            return f'(!{self.ex(e[0])})'
+        # But do add more if the user's being silly
+        return f'(!({self.ex(e[0])}))'
 
     def _ex_if(self, e):
-        ite = (self.ex(e._i), self.ex(e._t), self.ex(e._e))
-        if self._fcond is None:
-            return '(%s ? %s : %s)' % ite
-        else:
-            return '%s(%s, %s, %s)' % ((self._fcond,) + ite)
+        _if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
+        # If i is not a condtion (which always gets brackets from this writer)
+        # then add brackets
+        if not isinstance(e._i, myokit.Condition):
+            _if = f'({_if})'
+        return f'({_if} ? {_then} : {_else})'
+
+    def _ex_piecewise(self, e):
+        # Render ifs; add extra bracket if not a condition (see _ex_if)
+        _ifs = [self.ex(x) if isinstance(x, myokit.Condition)
+                else f'({self.ex(x)})' for x in e._i]
+        _thens = [self.ex(x) for x in e._e]
+
+        s = []
+        n = len(_ifs)
+        for _if, _then in zip(_ifs, _thens):
+            s.append(f'({_if} ? {_then} : ')
+        s.append(_thens[-1])
+        s.append(')' * len(_ifs))
+        return ''.join(s)
+
+
+class AnsiCExpressionWriter(CBasedExpressionWriter):
+    """
+    This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` writes
+    equations for variables in a C-style syntax.
+    """
+    def __init__(self):
+        super().__init__()
+        self._fcond = None
+
+    def set_condition_function(self, func=None):
+        """
+        Sets a function name to use for :class:`myokit.If`; if not set the
+        ternary operatur will be used.
+
+        If given, the function arguments should be ``(condition, value_if_true,
+        value_if_false)``. To revert to using the ternary operator, call with
+        ``func=None``.
+        """
+        self._fcond = func
+
+    #def _ex_name(self, e):
+    #def _ex_derivative(self, e):
+
+    def _ex_initial_value(self, e):
+        # These are disabled by default, but enabled here to support CVODES
+        # sensitivity calculations.
+        return self._flhs(e)
+
+    def _ex_partial_derivative(self, e):
+        # These are disabled by default, but enabled here to support CVODES
+        # sensitivity calculations.
+        return self._flhs(e)
+
+    #def _ex_number(self, e):
+
+    #def _ex_prefix_plus(self, e):
+    #def _ex_prefix_minus(self, e):
+
+    #def _ex_plus(self, e):
+    #def _ex_minus(self, e):
+    #def _ex_multiply(self, e):
+    #def _ex_divide(self, e):
+
+    #def _ex_quotient(self, e):
+    #def _ex_remainder(self, e):
+    #def _ex_power(self, e):
+    #def _ex_sqrt(self, e):
+    #def _ex_sin(self, e):
+    #def _ex_cos(self, e):
+    #def _ex_tan(self, e):
+    #def _ex_asin(self, e):
+    #def _ex_acos(self, e):
+    #def _ex_atan(self, e):
+    #def _ex_exp(self, e):
+    #def _ex_log(self, e):
+    #def _ex_log10(self, e):
+    #def _ex_floor(self, e):
+    #def _ex_ceil(self, e):
+    #def _ex_abs(self, e):
+    #def _ex_equal(self, e):
+    #def _ex_not_equal(self, e):
+    #def _ex_more(self, e):
+    #def _ex_less(self, e):
+    #def _ex_more_equal(self, e):
+    #def _ex_less_equal(self, e):
+    #def _ex_and(self, e):
+    #def _ex_or(self, e):
+    #def _ex_not(self, e):
+
+    def _ex_if(self, e):
+        # Allow _fcond
+
+        _if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
+        # If i is not a condtion (which always gets brackets from this writer)
+        # then add brackets
+        if not isinstance(e._i, myokit.Condition):
+            _if = f'({_if})'
+
+        # Use if-then-else function?
+        if self._fcond is not None:
+            return f'{self._fcond}({_if}, {_then}, {_else})'
+
+        # Default: use ternary operator
+        return f'({_if} ? {_then} : {_else})'
 
     def _ex_piecewise(self, e):
+        # Allow _fcond
+
+        # Render ifs; add extra bracket if not a condition (see _ex_if)
+        _ifs = [self.ex(x) if isinstance(x, myokit.Condition)
+                else f'({self.ex(x)})' for x in e._i]
+        _thens = [self.ex(x) for x in e._e]
+
         s = []
-        n = len(e._i)
-        if self._fcond is None:
-            for i in range(0, n):
-                s.append('(%s ? %s : ' % (self.ex(e._i[i]), self.ex(e._e[i])))
-            s.append(self.ex(e._e[n]))
-            s.append(')' * n)
+        n = len(_ifs)
+        if self._fcond is not None:
+            for _if, _then in zip(_ifs, _thens):
+                s.append(f'{self._fcond}({_if}, {_then}, ')
         else:
-            for i in range(0, n):
-                s.append(
-                    '%s(%s, %s, ' % (
-                        self._fcond, self.ex(e._i[i]), self.ex(e._e[i])))
-            s.append(self.ex(e._e[n]))
-            s.append(')' * n)
+            for _if, _then in zip(_ifs, _thens):
+                s.append(f'({_if} ? {_then} : ')
+        s.append(_thens[-1])
+        s.append(')' * len(_ifs))
         return ''.join(s)
 
diff --git a/myokit/formats/cpp/_ewriter.py b/myokit/formats/cpp/_ewriter.py
index c6a8d100b..58bdb1ed5 100644
--- a/myokit/formats/cpp/_ewriter.py
+++ b/myokit/formats/cpp/_ewriter.py
@@ -12,4 +12,15 @@ class CppExpressionWriter(AnsiCExpressionWriter):
     This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` translates
     myokit :class:`expressions <myokit.Expression>` to their C++ equivalent.
     """
-    pass
+    # Note: The set_condition_function is used by the jacobian calculator
+    # Once that has gone, this can base off the CBasedExpressionWriter without
+    # any modifications (so two methods below can then go).
+
+    def _ex_initial_value(self, e):
+        raise NotImplementedError(
+            'Initial values are not supported by this expression writer.')
+
+    def _ex_partial_derivative(self, e):
+        raise NotImplementedError(
+            'Partial derivatives are not supported by this expression writer.')
+
diff --git a/myokit/formats/cuda/_ewriter.py b/myokit/formats/cuda/_ewriter.py
index b20c9eacb..af19b3ecb 100644
--- a/myokit/formats/cuda/_ewriter.py
+++ b/myokit/formats/cuda/_ewriter.py
@@ -6,24 +6,26 @@
 #
 import myokit
 
-from myokit.formats.python import PythonExpressionWriter
+from myokit.formats.ansic import CBasedExpressionWriter
 
 
-class CudaExpressionWriter(PythonExpressionWriter):
+class CudaExpressionWriter(CBasedExpressionWriter):
     """
     This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` translates
     Myokit :class:`expressions <myokit.Expression>` to their CUDA equivalents.
     """
     def __init__(self, precision=myokit.SINGLE_PRECISION):
         super().__init__()
-        self._function_prefix = ''
         self._sp = (precision == myokit.SINGLE_PRECISION)
 
     #def _ex_name(self, e):
     #def _ex_derivative(self, e):
+    #def _ex_initial_value(self, e):
+    #def _ex_partial_derivative(self, e):
 
     def _ex_number(self, e):
-        return myokit.float.str(e) + 'f' if self._sp else myokit.float.str(e)
+        x = super()._ex_number(e)
+        return x + 'f' if self._sp else x
 
     #def _ex_prefix_plus(self, e):
     #def _ex_prefix_minus(self, e):
@@ -32,30 +34,12 @@ def _ex_number(self, e):
     #def _ex_multiply(self, e):
     #def _ex_divide(self, e):
 
-    def _ex_quotient(self, e):
-        # Note that this _must_ round towards minus infinity.
-        # See myokit.Quotient
-        # CUDA docs are unclear on convention, so assuming it follows C and
-        # so we need a custom implementation.
-        return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
-
-    def _ex_remainder(self, e):
-        # Note that this _must_ use the same round-to-neg-inf convention as
-        # myokit.Quotient.
-        # CUDA docs are unclear on convention, so assuming it follows C and
-        # so we need a custom implementation.
-        return self.ex(myokit.Minus(
-            e[0], myokit.Multiply(e[1], myokit.Quotient(e[0], e[1]))))
+    #def _ex_quotient(self, e):
+    #def _ex_remainder(self, e):
 
     def _ex_power(self, e):
-        if e[1] == myokit.Number(2):
-            if e.bracket(e[0]):
-                return '((' + self.ex(e[0]) + ') * (' + self.ex(e[0]) + '))'
-            else:
-                return '(' + self.ex(e[0]) + ' * ' + self.ex(e[0]) + ')'
-        else:
-            f = 'powf' if self._sp else 'pow'
-            return f + '(' + self.ex(e[0]) + ', ' + self.ex(e[1]) + ')'
+        pow = 'powf' if self._sp else 'pow'
+        return f'{pow}({self.ex(e[0])}, {self.ex(e[1])})'
 
     def _ex_sqrt(self, e):
         f = 'sqrtf' if self._sp else 'sqrt'
@@ -112,35 +96,15 @@ def _ex_abs(self, e):
         f = 'fabsf' if self._sp else 'fabs'
         return self._ex_function(e, f)
 
-    def _ex_not(self, e):
-        return '!(' + self.ex(e[0]) + ')'
-
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
     #def _ex_more(self, e):
     #def _ex_less(self, e):
     #def _ex_more_equal(self, e):
     #def _ex_less_equal(self, e):
-
-    def _ex_and(self, e):
-        return self._ex_infix_condition(e, '&&')
-
-    def _ex_or(self, e):
-        return self._ex_infix_condition(e, '||')
-
-    def _ex_if(self, e):
-        return '(%s ? %s : %s)' % (self.ex(e._i), self.ex(e._t), self.ex(e._e))
-
-    def _ex_piecewise(self, e):
-        s = []
-        n = len(e._i)
-        for i in range(0, n):
-            s.append('(')
-            s.append(self.ex(e._i[i]))
-            s.append(' ? ')
-            s.append(self.ex(e._e[i]))
-            s.append(' : ')
-        s.append(self.ex(e._e[n]))
-        s.append(')' * n)
-        return ''.join(s)
+    #def _ex_and(self, e):
+    #def _ex_or(self, e):
+    #def _ex_not(self, e):
+    #def _ex_if(self, e):
+    #def _ex_piecewise(self, e):
 
diff --git a/myokit/formats/easyml/_ewriter.py b/myokit/formats/easyml/_ewriter.py
index dbb3616f6..28c5cabf6 100644
--- a/myokit/formats/easyml/_ewriter.py
+++ b/myokit/formats/easyml/_ewriter.py
@@ -1,6 +1,9 @@
 #
 # EasyML expression writer
 #
+# Supported functions:
+#  https://opencarp.org/documentation/examples/01_ep_single_cell/05_easyml
+#
 # This file is part of Myokit.
 # See http://myokit.org for copyright, sharing, and licensing details.
 #
@@ -8,24 +11,22 @@
 
 import myokit
 
-from myokit.formats.python import PythonExpressionWriter
+from myokit.formats.ansic import CBasedExpressionWriter
 
 
-class EasyMLExpressionWriter(PythonExpressionWriter):
+class EasyMLExpressionWriter(CBasedExpressionWriter):
     """
     This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` writes
     equations for variables in EasyML syntax.
+
+    EasyML has a C-like syntax, and uses C operator precedence.
     """
     def __init__(self):
         super().__init__()
-        self._function_prefix = ''
 
     #def _ex_name(self, e):
     #def _ex_derivative(self, e):
-
-    def _ex_number(self, e):
-        return myokit.float.str(e)
-
+    #def _ex_number(self, e):
     #def _ex_prefix_plus(self, e):
     #def _ex_prefix_minus(self, e):
     #def _ex_plus(self, e):
@@ -33,69 +34,52 @@ def _ex_number(self, e):
     def _ex_minus(self, e):
         if isinstance(e[0], myokit.Exp) and isinstance(e[1], myokit.Number):
             if e[1].eval() == 1:
-                return 'expm1(' + self.ex(e[0][0]) + ')'
+                return f'expm1({self.ex(e[0][0])})'
         if isinstance(e[1], myokit.Exp) and isinstance(e[0], myokit.Number):
             if e[0].eval() == 1:
-                return '-expm1(' + self.ex(e[1][0]) + ')'
+                return f'-expm1({self.ex(e[1][0])})'
         return super()._ex_minus(e)
 
     #def _ex_multiply(self, e):
     #def _ex_divide(self, e):
-
-    def _ex_quotient(self, e):
-        return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
-
-    def _ex_remainder(self, e):
-        return self.ex(myokit.Minus(
-            e[0], myokit.Multiply(e[1], myokit.Quotient(e[0], e[1]))))
-
-    def _ex_power(self, e):
-        return 'pow(' + self.ex(e[0]) + ', ' + self.ex(e[1]) + ')'
-
+    #def _ex_quotient(self, e):
+    #def _ex_remainder(self, e):
+    #def _ex_power(self, e):
     #def _ex_sqrt(self, e):
 
     def _ex_sin(self, e):
-        warnings.warn('Potentially unsupported function: sin()')
+        warnings.warn('Unsupported function: sin()')
         return super()._ex_sin(e)
 
     #def _ex_cos(self, e):
 
     def _ex_tan(self, e):
-        warnings.warn('Potentially unsupported function: tan()')
+        warnings.warn('Unsupported function: tan()')
         return super()._ex_tan(e)
 
     def _ex_asin(self, e):
-        warnings.warn('Potentially unsupported function: asin()')
+        warnings.warn('Unsupported function: asin()')
         return super()._ex_asin(e)
 
     #def _ex_acos(self, e):
 
     def _ex_atan(self, e):
-        warnings.warn('Potentially unsupported function: atan()')
+        warnings.warn('Unsupported function: atan()')
         return super()._ex_atan(e)
 
     #def _ex_exp(self, e):
-
-    def _ex_log(self, e):
-        if len(e) == 1:
-            return self._ex_function(e, 'log')
-        return '(log(' + self.ex(e[0]) + ') / log(' + self.ex(e[1]) + '))'
-
+    #def _ex_log(self, e):
     #def _ex_log10(self, e):
 
     def _ex_floor(self, e):
-        warnings.warn('Potentially unsupported function: floor()')
+        warnings.warn('Unsupported function: floor()')
         return super()._ex_floor(e)
 
     def _ex_ceil(self, e):
-        warnings.warn('Potentially unsupported function: ceil()')
+        warnings.warn('Unsupported function: ceil()')
         return super()._ex_ceil(e)
 
-    def _ex_abs(self, e):
-        return self._ex_function(e, 'fabs')
-
-    def _ex_not(self, e):
-        return '!(' + self.ex(e[0]) + ')'
+    #def _ex_abs(self, e):
 
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
@@ -104,22 +88,10 @@ def _ex_not(self, e):
     #def _ex_more_equal(self, e):
     #def _ex_less_equal(self, e):
 
-    def _ex_and(self, e):
-        return self._ex_infix_condition(e, 'and')
-
-    def _ex_or(self, e):
-        return self._ex_infix_condition(e, 'or')
-
-    def _ex_if(self, e):
-        ite = (self.ex(e._i), self.ex(e._t), self.ex(e._e))
-        return '(%s ? %s : %s)' % ite
+    #def _ex_not(self, e):
+    #def _ex_and(self, e):
+    #def _ex_or(self, e):
 
-    def _ex_piecewise(self, e):
-        s = []
-        n = len(e._i)
-        for i in range(0, n):
-            s.append('(%s ? %s : ' % (self.ex(e._i[i]), self.ex(e._e[i])))
-        s.append(self.ex(e._e[n]))
-        s.append(')' * n)
-        return ''.join(s)
+    #def _ex_if(self, e):
+    #def _ex_piecewise(self, e):
 
diff --git a/myokit/formats/latex/_ewriter.py b/myokit/formats/latex/_ewriter.py
index ff082df33..d59ac24d4 100644
--- a/myokit/formats/latex/_ewriter.py
+++ b/myokit/formats/latex/_ewriter.py
@@ -20,19 +20,29 @@ def __init__(self):
         self.set_time_variable_name()
 
         # Default lhs function
-        def fhls(lhs):
-            var = self._prepare_name(lhs.var().uname())
-            var = '\\text{' + var + '}'     # Depends on amsmath package!
-            if isinstance(lhs, myokit.Derivative):
-                var = '\\frac{d}{d\\text{t}}' + var
-            return var
+        def flhs(lhs):
+            v = r'\text{' + self._prepare_name(lhs.var().uname()) + '}'
+            if isinstance(lhs, myokit.Name):
+                return v
+            elif isinstance(lhs, myokit.Derivative):
+                return r'\frac{d' + v + r'}{d\text{' + self._time + '}}'
+            elif isinstance(lhs, myokit.PartialDerivative):
+                v2 = self._prepare_name(
+                    lhs.independent_expression().var().uname())
+                return r'\frac{\partial' + v + r'}{\partial\text{' + v2 + '}}'
+            elif isinstance(lhs, myokit.InitialValue):
+                return v + r'(\text{' + self._time + '} = 0)'
+            else:   # pragma: no cover
+                raise ValueError(f'Unsupported LHS type {type(lhs)}')
+
         self._flhs = None
-        self.set_lhs_function(fhls)
+        self.set_lhs_function(flhs)
 
     def set_lhs_function(self, f):
         """
         Sets a naming function, will be called to get the variable name from a
-         ``myokit.LhsExpression`` object.
+         :class:`myokit.LhsExpression`. This function is also responsible for
+         converting it to a suitable Latex form (e.g. adding `\\text{}`).
 
         The argument ``f`` should be a function that takes an ``LhsExpression``
         as input and returns a string.
@@ -40,31 +50,22 @@ def set_lhs_function(self, f):
         self._flhs = f
 
     def set_time_variable_name(self, name='t'):
-        """
-        Sets a name to use for the time variable in derivatives
-        """
+        """ Sets a name to use for the time variable in derivatives. """
         self._time = self._prepare_name(name)
 
     def eq(self, eq):
-        """
-        Converts an equation to a string.
-        """
+        """ See :meth:`myokit.formats.ExpressionWriter.eq()`. """
         return self.ex(eq.lhs) + ' = ' + self.ex(eq.rhs)
 
     def ex(self, e):
-        """
-        Converts an expression to a string.
-        """
+        """ See :meth:`myokit.formats.ExpressionWriter.ex()`. """
         b = []
         self._ex(e, b)
         return ''.join(b)
 
     def _prepare_name(self, text):
-        """
-        Prepares a name for use in latex
-        """
-        text = str(text)
-        return text.replace('_', r'\_')
+        """ Sanitises a name for use in latex. """
+        return str(text).replace('_', r'\_')
 
     def _ex(self, e, b):
         try:
@@ -73,40 +74,43 @@ def _ex(self, e, b):
             raise ValueError('Unknown expression type: ' + str(type(e)))
         action(e, b)
 
+    def _ex_prefix(self, e, b, op):
+        """ Handles _ex() for prefix operators. """
+        b.append(op)
+        if e.bracket(e[0]):
+            b.append(r'\left(')
+        self._ex(e[0], b)
+        if e.bracket(e[0]):
+            b.append(r'\right)')
+
     def _ex_infix(self, e, b, op):
-        """
-        Handles _ex() for infix operators
-        """
+        """ Handles _ex() for infix operators. """
         if e.bracket(e[0]):
-            b.append('\\left(')
+            b.append(r'\left(')
         self._ex(e[0], b)
         if e.bracket(e[0]):
-            b.append('\\right)')
+            b.append(r'\right)')
         b.append(op)
         if e.bracket(e[1]):
-            b.append('\\left(')
+            b.append(r'\left(')
         self._ex(e[1], b)
         if e.bracket(e[1]):
-            b.append('\\right)')
+            b.append(r'\right)')
 
     def _ex_function(self, e, b, func):
-        """
-        Handles _ex() for function operators
-        """
+        """ Handles _ex() for function operators. """
         b.append(func)
-        b.append('\\left(')
+        b.append(r'\left(')
         b.append(','.join([self.ex(x) for x in e]))
-        b.append('\\right)')
+        b.append(r'\right)')
 
     def _ex_infix_condition(self, e, b, op):
-        """
-        Handles _ex() for infix condition operators
-        """
-        b.append('\\left(')
+        """ Handles _ex() for infix condition operators. """
+        b.append(r'\left(')
         self._ex(e[0], b)
         b.append(op)
         self._ex(e[1], b)
-        b.append('\\right)')
+        b.append(r'\right)')
 
     def _ex_name(self, e, b):
         b.append(self._flhs(e))
@@ -114,21 +118,24 @@ def _ex_name(self, e, b):
     def _ex_derivative(self, e, b):
         b.append(self._flhs(e))
 
+    def _ex_partial_derivative(self, e, b):
+        b.append(self._flhs(e))
+
+    def _ex_initial_value(self, e, b):
+        b.append(self._flhs(e))
+
     def _ex_number(self, e, b):
-        b.append(myokit.float.str(e))
+        b.append(myokit.float.str(e).strip())
+        u = e.unit()
+        if u is not None and u is not myokit.units.dimensionless:
+            u = str(u)[1:-1]
+            b.append(r' \text{' + u + '}')
 
     def _ex_prefix_plus(self, e, b):
-        self._ex(e[0], b)
+        self._ex_prefix(e, b, '+')
 
     def _ex_prefix_minus(self, e, b):
-        b.append('\\left(')
-        b.append('-')
-        if e.bracket(e[0]):
-            b.append('\\left(')
-        self._ex(e[0], b)
-        if e.bracket(e[0]):
-            b.append('\\right)')
-        b.append('\\right)')
+        self._ex_prefix(e, b, '-')
 
     def _ex_plus(self, e, b):
         self._ex_infix(e, b, '+')
@@ -137,10 +144,10 @@ def _ex_minus(self, e, b):
         self._ex_infix(e, b, '-')
 
     def _ex_multiply(self, e, b):
-        self._ex_infix(e, b, '*')
+        self._ex_infix(e, b, r'\cdot')
 
     def _ex_divide(self, e, b):
-        b.append('\\frac{')
+        b.append(r'\frac{')
         self._ex(e[0], b)
         b.append('}{')
         self._ex(e[1], b)
@@ -149,85 +156,88 @@ def _ex_divide(self, e, b):
     def _ex_quotient(self, e, b):
         # Note: Quotient in myokit uses round-to-zero (like Python does!)
         # See: myokit.Quotient
-        b.append('\\left\\lfloor')
+        b.append(r'\left\lfloor')
         self._ex_divide(e, b)
-        b.append('\\right\\rfloor')
+        b.append(r'\right\rfloor')
 
     def _ex_remainder(self, e, b):
-        self._ex_function(e, b, '\\bmod')
+        self._ex_infix(e, b, r'\bmod')
 
     def _ex_power(self, e, b):
-        if e.bracket(e[0]):
-            b.append('\\left(')
+        if e.bracket(e[0]) or isinstance(e[0], myokit.Power):
+            b.append(r'\left(')
         self._ex(e[0], b)
-        if e.bracket(e[0]):
-            b.append('\\right)')
-        b.append('^{')
+        if e.bracket(e[0]) or isinstance(e[0], myokit.Power):
+            b.append(r'\right)')
+        b.append('^')
+        if e.bracket(e[1]) or isinstance(e[1], myokit.Power):
+            b.append('{')
         self._ex(e[1], b)
-        b.append('}')
+        if e.bracket(e[1]) or isinstance(e[1], myokit.Power):
+            b.append('}')
 
     def _ex_sqrt(self, e, b):
-        b.append('\\sqrt{')
+        b.append(r'\sqrt{')
         self._ex(e[0], b)
         b.append('}')
 
     def _ex_sin(self, e, b):
-        self._ex_function(e, b, '\\sin')
+        self._ex_function(e, b, r'\sin')
 
     def _ex_cos(self, e, b):
-        self._ex_function(e, b, '\\cos')
+        self._ex_function(e, b, r'\cos')
 
     def _ex_tan(self, e, b):
-        self._ex_function(e, b, '\\tan')
+        self._ex_function(e, b, r'\tan')
 
     def _ex_asin(self, e, b):
-        self._ex_function(e, b, '\\arcsin')
+        self._ex_function(e, b, r'\arcsin')
 
     def _ex_acos(self, e, b):
-        self._ex_function(e, b, '\\arccos')
+        self._ex_function(e, b, r'\arccos')
 
     def _ex_atan(self, e, b):
-        self._ex_function(e, b, '\\arctan')
+        self._ex_function(e, b, r'\arctan')
 
     def _ex_exp(self, e, b):
-        self._ex_function(e, b, '\\exp')
+        self._ex_function(e, b, r'\exp')
 
     def _ex_log(self, e, b):
-        b.append('\\log')
+        b.append(r'\log')
         if len(e) > 1:
             b.append('_{')
             self._ex(e[1], b)
             b.append('}')
-        b.append('\\left(')
+        b.append(r'\left(')
         self._ex(e[0], b)
-        b.append('\\right)')
+        b.append(r'\right)')
 
     def _ex_log10(self, e, b):
-        return self._ex_log(myokit.Log(e[0], myokit.Number(10)), b)
+        b.append(r'\log_{10}')
+        b.append(r'\left(')
+        self._ex(e[0], b)
+        b.append(r'\right)')
 
     def _ex_floor(self, e, b):
-        b.append('\\left\\lfloor{')
+        b.append(r'\left\lfloor{')
         self._ex(e[0], b)
-        b.append('}\\right\\rfloor')
+        b.append(r'}\right\rfloor')
 
     def _ex_ceil(self, e, b):
-        b.append('\\left\\lceil{')
+        b.append(r'\left\lceil{')
         self._ex(e[0], b)
-        b.append('}\\right\\rceil')
+        b.append(r'}\right\rceil')
 
     def _ex_abs(self, e, b):
-        b.append('\\lvert{')
+        b.append(r'\lvert{')
         self._ex(e[0], b)
-        b.append('}\\rvert')
-
-    def _ex_not(self, e, b):
-        self._ex_function(e, b, '\\not')
+        b.append(r'}\rvert')
 
     def _ex_equal(self, e, b):
         self._ex_infix_condition(e, b, '=')
 
     def _ex_not_equal(self, e, b):
-        self._ex_infix_condition(e, b, '\\neq')
+        self._ex_infix_condition(e, b, r'\neq')
 
     def _ex_more(self, e, b):
         self._ex_infix_condition(e, b, '>')
@@ -236,21 +246,26 @@ def _ex_less(self, e, b):
         self._ex_infix_condition(e, b, '<')
 
     def _ex_more_equal(self, e, b):
-        self._ex_infix_condition(e, b, '\\geq')
+        self._ex_infix_condition(e, b, r'\geq')
 
     def _ex_less_equal(self, e, b):
-        self._ex_infix_condition(e, b, '\\leq')
+        self._ex_infix_condition(e, b, r'\leq')
+
+    def _ex_not(self, e, b):
+        b.append(r'\left(\not')
+        self._ex(e[0], b)
+        b.append(r'\right)')
 
     def _ex_and(self, e, b):
-        self._ex_infix_condition(e, b, '\\and')
+        self._ex_infix_condition(e, b, r'\and')
 
     def _ex_or(self, e, b):
-        self._ex_infix_condition(e, b, '\\or')
+        self._ex_infix_condition(e, b, r'\or')
 
     def _ex_if(self, e, b):
         # Not suported
-        self._ex_function(e, b, 'if')
+        self._ex_function(e, b, r'\text{if}')
 
     def _ex_piecewise(self, e, b):
         # Not suported
-        self._ex_function(e, b, 'piecewise')
+        self._ex_function(e, b, r'\text{piecewise}')
diff --git a/myokit/formats/mathml/_ewriter.py b/myokit/formats/mathml/_ewriter.py
index 4a6fad78c..fd6db1e63 100644
--- a/myokit/formats/mathml/_ewriter.py
+++ b/myokit/formats/mathml/_ewriter.py
@@ -130,11 +130,11 @@ def _ex_prefix(self, e, t, cml):
         bra = e.bracket(e[0])
         if self._pres:
             row = etree.SubElement(t, 'mrow')
+            x = etree.SubElement(row, 'mo')
+            x.text = e.operator_rep()
             if bra:
                 x = etree.SubElement(row, 'mo')
                 x.text = '('
-            x = etree.SubElement(row, 'mo')
-            x.text = e.operator_rep()
             self._ex(e[0], row)
             if bra:
                 x = etree.SubElement(row, 'mo')
diff --git a/myokit/formats/matlab/_ewriter.py b/myokit/formats/matlab/_ewriter.py
index 8214252d0..9306bfd46 100644
--- a/myokit/formats/matlab/_ewriter.py
+++ b/myokit/formats/matlab/_ewriter.py
@@ -4,6 +4,8 @@
 # This file is part of Myokit.
 # See http://myokit.org for copyright, sharing, and licensing details.
 #
+import myokit
+
 from myokit.formats.python import PythonExpressionWriter
 
 
@@ -17,15 +19,32 @@ def __init__(self):
         self._function_prefix = ''
         self._fcond = 'ifthenelse'
 
-    def set_condition_function(self, func=None):
+    def set_condition_function(self, func):
         """
-        Sets a function name to use for if statements
+        Sets a function name to output to handle :class:`myokit.If`.
+
+        The function must take arguments ``(condition, value_if_true,
+        value_if_false)`` and will be used to handle both :class:`myokit.If`
+        and :class:`myokit.Piecewise`.
+
+        By default, `ifthenelse` is used, which the user is expected to define
+        if ``If`` or ``Piecewise`` will be used. For example::
+
+            function y = ifthenelse(condition, value_if_true, value_if_false)
+                if (condition)
+                    y = value_if_true;
+                else
+                    y = value_if_false;
+                end
+            end
 
-        By setting func to None you can revert back to the default behavior
-         (the ternary operator). Any other value will be interpreted as the
-         name of a function taking arguments (condition, value_if_true,
-         value_if_false).
         """
+        if func is not None:
+            func = str(func).strip()
+        if func is None or func == '':
+            raise ValueError(
+                'The MatlabExpressionWriter needs a condition function to be'
+                ' set.')
         self._fcond = func
 
     #def _ex_name(self, e):
@@ -40,14 +59,17 @@ def set_condition_function(self, func=None):
 
     def _ex_quotient(self, e):
         # Round towards minus infinity
-        return 'floor(' + self._ex_infix(e, '/') + ')'
+        return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
 
     def _ex_remainder(self, e):
         # Uses the round-towards-minus-infinity convention!
-        return 'mod(' + self.ex(e[0]) + ', ' + self.ex(e[1]) + ')'
+        return f'mod({self.ex(e[0])}, {self.ex(e[1])})'
 
     def _ex_power(self, e):
-        return self._ex_infix(e, '^')
+        # Same associativity as Myokit, not Python! So override
+        e1 = f'({self.ex(e[0])})' if e.bracket(e[0]) else f'{self.ex(e[0])}'
+        e2 = f'({self.ex(e[1])})' if e.bracket(e[1]) else f'{self.ex(e[1])}'
+        return f'{e1}^{e2}'
 
     #def _ex_sqrt(self, e):
     #    Ignore imaginary part
@@ -63,8 +85,9 @@ def _ex_power(self, e):
     def _ex_log(self, e):
         if len(e) == 1:
             return self._ex_function(e, 'log')
-        else:
-            return '(log(' + self.ex(e[0]) + ') / log(' + self.ex(e[1]) + '))'
+        # Always add brackets: Parent was expecting a function so will never
+        # have added them.
+        return f'(log({self.ex(e[0])}) / log({self.ex(e[1])}))'
 
     #def _ex_log10(self, e):
     #def _ex_floor(self, e):
@@ -73,9 +96,6 @@ def _ex_log(self, e):
     def _ex_abs(self, e):
         return self._ex_function(e, 'abs')
 
-    def _ex_not(self, e):
-        return '!(' + self.ex(e[0]) + ')'
-
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
     #def _ex_more(self, e):
@@ -84,26 +104,28 @@ def _ex_not(self, e):
     #def _ex_less_equal(self, e):
 
     def _ex_and(self, e):
-        return self._ex_infix_condition(e, '&&')
+        return self._ex_infix_logical(e, '&&')
 
     def _ex_or(self, e):
-        return self._ex_infix_condition(e, '||')
+        return self._ex_infix_logical(e, '||')
+
+    def _ex_not(self, e):
+        return f'(!{self.ex(e[0])})'
 
     def _ex_if(self, e):
-        return '%s(%s, %s, %s)' % (
-            self._fcond, self.ex(e._i), self.ex(e._t), self.ex(e._e))
+        _if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
+        return f'{self._fcond}({_if}, {_then}, {_else})'
 
     def _ex_piecewise(self, e):
+        # Render ifs; add extra bracket if not a condition (see _ex_if)
+        _ifs = [self.ex(x) for x in e._i]
+        _thens = [self.ex(x) for x in e._e]
+
         s = []
-        n = len(e._i)
-        for i in range(0, n):
-            s.append(self._fcond)
-            s.append('(')
-            s.append(self.ex(e._i[i]))
-            s.append(', ')
-            s.append(self.ex(e._e[i]))
-            s.append(', ')
-        s.append(self.ex(e._e[n]))
-        s.append(')' * n)
+        n = len(_ifs)
+        for _if, _then in zip(_ifs, _thens):
+            s.append(f'{self._fcond}({_if}, {_then}, ')
+        s.append(_thens[-1])
+        s.append(')' * len(_ifs))
         return ''.join(s)
 
diff --git a/myokit/formats/opencl/_ewriter.py b/myokit/formats/opencl/_ewriter.py
index 2f176551f..4d2b805b9 100644
--- a/myokit/formats/opencl/_ewriter.py
+++ b/myokit/formats/opencl/_ewriter.py
@@ -6,47 +6,60 @@
 #
 import myokit
 
-from myokit.formats.python import PythonExpressionWriter
+from myokit.formats.ansic import CBasedExpressionWriter
 
 
-class OpenCLExpressionWriter(PythonExpressionWriter):
+class OpenCLExpressionWriter(CBasedExpressionWriter):
     """
     This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` translates
     Myokit :class:`expressions <myokit.Expression>` to OpenCL syntax.
+
+    Arguments:
+
+    ``precision``
+        By default, numbers are shown as e.g. ``1.23f``, denoting single
+        precision literals. To use double precision instead, set ``precision``
+        to ``myokit.DOUBLE_PRECISION``.
+    ``native_math``
+        By default, the software implementations of functions like ``log`` and
+        ``exp`` are used. To use the native version instead, set
+        ``native_math`` to ``True``.
+
     """
     def __init__(self, precision=myokit.SINGLE_PRECISION, native_math=True):
         super().__init__()
-        self._function_prefix = ''
         self._sp = (precision == myokit.SINGLE_PRECISION)
         self._nm = bool(native_math)
 
     def _exc(self, e):
         """Returns ``ex(e)`` if ``e`` is a Condition, else ``ex(e != 0)``."""
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
         if isinstance(e, myokit.Condition):
             return self.ex(e)
         return self.ex(myokit.NotEqual(e, myokit.Number(0)))
 
-    def _ex_infix_condition(self, e, op):
+    def _ex_infix_comparison(self, e, op):
         """Handles ex() for infix condition operators (==, !=, > etc.)."""
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
         c1 = isinstance(e[0], myokit.Condition)
         c2 = isinstance(e[1], myokit.Condition)
         if (c1 and c2) or not (c1 or c2):
-            a = self.ex(e[0])
-            b = self.ex(e[1])
+            return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
         else:
-            a = self._exc(e[0])
-            b = self._exc(e[1])
-        return '(' + a + ' ' + op + ' ' + b + ')'
+            return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
 
     def _ex_infix_logical(self, e, op):
-        """Handles ex() for infix logical operators."""
-        return '(' + self._exc(e[0]) + ' ' + op + ' ' + self._exc(e[1]) + ')'
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
+        return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
 
     #def _ex_name(self, e):
     #def _ex_derivative(self, e):
+    #def _ex_initial_value(self, e):
+    #def _ex_partial_derivative(self, e):
 
     def _ex_number(self, e):
-        return myokit.float.str(e) + 'f' if self._sp else myokit.float.str(e)
+        x = super()._ex_number(e)
+        return x + 'f' if self._sp else x
 
     #def _ex_prefix_plus(self, e):
     #def _ex_prefix_minus(self, e):
@@ -54,34 +67,15 @@ def _ex_number(self, e):
     #def _ex_minus(self, e):
     #def _ex_multiply(self, e):
 
-    def _ex_divide(self, e):
-        # Native divide seemed to cause some issues
-        #if self._nm:
-        #    return 'native_divide(' + self.ex(e[0]) +', '+ self.ex(e[1]) + ')'
-        #else:
-        return self._ex_infix(e, '/')
-
-    def _ex_quotient(self, e):
-        # Note that this _must_ round towards minus infinity.
-        # See myokit.Quotient.
-        # Assuming it follows C and so we need a custom implementation.
-        return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
-
-    def _ex_remainder(self, e):
-        # Note that this _must_ use the same round-to-neg-inf convention as
-        # myokit.Quotient.
-        # Assuming it follows C and so we need a custom implementation.
-        return self.ex(myokit.Minus(
-            e[0], myokit.Multiply(e[1], myokit.Quotient(e[0], e[1]))))
-
-    def _ex_power(self, e):
-        if e[1] == myokit.Number(2):
-            if e.bracket(e[0]):
-                return '((' + self.ex(e[0]) + ') * (' + self.ex(e[0]) + '))'
-            else:
-                return '(' + self.ex(e[0]) + ' * ' + self.ex(e[0]) + ')'
-        else:
-            return 'pow(' + self.ex(e[0]) + ', ' + self.ex(e[1]) + ')'
+    # Native divide seemed to cause some issues
+    #def _ex_divide(self, e):
+    #    if self._nm:
+    #        return 'native_divide(' + self.ex(e[0]) +', '+ self.ex(e[1]) + ')'
+    #    return self._ex_infix(e, '/')
+
+    #def _ex_quotient(self, e):
+    #def _ex_remainder(self, e):
+    #def _ex_power(self, e):
 
     def _ex_sqrt(self, e):
         f = 'native_sqrt' if self._nm else 'sqrt'
@@ -99,41 +93,29 @@ def _ex_tan(self, e):
         f = 'native_tan' if self._nm else 'tan'
         return self._ex_function(e, f)
 
-    def _ex_asin(self, e):
-        return self._ex_function(e, 'asin')
-
-    def _ex_acos(self, e):
-        return self._ex_function(e, 'acos')
-
-    def _ex_atan(self, e):
-        return self._ex_function(e, 'atan')
+    #def _ex_asin(self, e):
+    #def _ex_acos(self, e):
+    #def _ex_atan(self, e):
 
     def _ex_exp(self, e):
         f = 'native_exp' if self._nm else 'exp'
         return self._ex_function(e, f)
 
     def _ex_log(self, e):
-        f = 'native_log' if self._nm else 'log'
+        log = 'native_log' if self._nm else 'log'
         if len(e) == 1:
-            return self._ex_function(e, f)
-        return '(' + f + '(' + self.ex(e[0]) + ') / ' + f + '(' \
-            + self.ex(e[1]) + '))'
+            return self._ex_function(e, log)
+        # Always add brackets: parent was expecting a function so will never
+        # have added them.
+        return f'({log}({self.ex(e[0])}) / {log}({self.ex(e[1])}))'
 
     def _ex_log10(self, e):
         f = 'native_log10' if self._nm else 'log10'
         return self._ex_function(e, f)
 
-    def _ex_floor(self, e):
-        return self._ex_function(e, 'floor')
-
-    def _ex_ceil(self, e):
-        return self._ex_function(e, 'ceil')
-
-    def _ex_abs(self, e):
-        return self._ex_function(e, 'fabs')
-
-    def _ex_not(self, e):
-        return '!(' + self._exc(e[0]) + ')'
+    #def _ex_floor(self, e):
+    #def _ex_ceil(self, e):
+    #def _ex_abs(self, e):
 
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
@@ -142,26 +124,27 @@ def _ex_not(self, e):
     #def _ex_more_equal(self, e):
     #def _ex_less_equal(self, e):
 
-    def _ex_and(self, e):
-        return self._ex_infix_logical(e, '&&')
+    #def _ex_and(self, e):
+    #def _ex_or(self, e):
 
-    def _ex_or(self, e):
-        return self._ex_infix_logical(e, '||')
+    def _ex_not(self, e):
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
+        return f'(!{self._exc(e[0])})'
 
     def _ex_if(self, e):
-        return '(%s ? %s : %s)' % (
-            self._exc(e._i), self.ex(e._t), self.ex(e._e))
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
+        _if, _then, _else = self._exc(e._i), self.ex(e._t), self.ex(e._e)
+        return f'({_if} ? {_then} : {_else})'
 
     def _ex_piecewise(self, e):
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
+        _ifs = [self._exc(x) for x in e._i]
+        _thens = [self.ex(x) for x in e._e]
         s = []
-        n = len(e._i)
-        for i in range(0, n):
-            s.append('(')
-            s.append(self._exc(e._i[i]))
-            s.append(' ? ')
-            s.append(self.ex(e._e[i]))
-            s.append(' : ')
-        s.append(self.ex(e._e[n]))
-        s.append(')' * n)
+        n = len(_ifs)
+        for _if, _then in zip(_ifs, _thens):
+            s.append(f'({_if} ? {_then} : ')
+        s.append(_thens[-1])
+        s.append(')' * len(_ifs))
         return ''.join(s)
 
diff --git a/myokit/formats/python/_ewriter.py b/myokit/formats/python/_ewriter.py
index 28b42fcbb..32ae7c25c 100644
--- a/myokit/formats/python/_ewriter.py
+++ b/myokit/formats/python/_ewriter.py
@@ -31,31 +31,37 @@ def set_lhs_function(self, f):
         """
         self._flhs = f
 
+    def _ex_prefix(self, e, op):
+        """ Handles ex() for prefix operators. """
+        # No simplifications should be made here for PrefixPlus, see
+        #   https://github.com/myokit/myokit/issues/1054
+        if e.bracket(e[0]):
+            return f'{op}({self.ex(e[0])})'
+        return f'{op}{self.ex(e[0])}'
+
     def _ex_infix(self, e, op):
-        """
-        Handles ex() for infix operators
-        """
+        """ Handles ex() for infix operators, except Power. """
         if e.bracket(e[0]):
-            out = '(' + self.ex(e[0]) + ') ' + op
+            out = f'({self.ex(e[0])}) {op}'
         else:
-            out = self.ex(e[0]) + ' ' + op
+            out = f'{self.ex(e[0])} {op}'
         if e.bracket(e[1]):
-            return out + ' (' + self.ex(e[1]) + ')'
+            return f'{out} ({self.ex(e[1])})'
         else:
-            return out + ' ' + self.ex(e[1])
+            return f'{out} {self.ex(e[1])}'
 
     def _ex_function(self, e, func):
-        """
-        Handles ex() for function operators
-        """
-        return self._function_prefix + func \
-            + '(' + ', '.join([self.ex(x) for x in e]) + ')'
+        """ Handles ex() for function operators with operands. """
+        args = ', '.join([self.ex(x) for x in e])
+        return f'{self._function_prefix}{func}({args})'
 
-    def _ex_infix_condition(self, e, op):
-        """
-        Handles ex() for infix condition operators
-        """
-        return '(' + self.ex(e[0]) + ' ' + op + ' ' + self.ex(e[1]) + ')'
+    def _ex_infix_comparison(self, e, op):
+        """ Handles ex() for infix comparisons (``==``, ``>=``, etc.). """
+        return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
+
+    def _ex_infix_logical(self, e, op):
+        """ Handles ex() for ``and`` and ``or``. """
+        return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
 
     def _ex_name(self, e):
         return self._flhs(e)
@@ -64,22 +70,21 @@ def _ex_derivative(self, e):
         return self._flhs(e)
 
     def _ex_initial_value(self, e):
-        return self._flhs(e)
+        raise NotImplementedError(
+            'Initial values are not supported by this expression writer.')
 
     def _ex_partial_derivative(self, e):
-        return self._flhs(e)
+        raise NotImplementedError(
+            'Partial derivatives are not supported by this expression writer.')
 
     def _ex_number(self, e):
-        return myokit.float.str(e)
+        return myokit.float.str(e).lstrip()
 
     def _ex_prefix_plus(self, e):
-        return self.ex(e[0])
+        return self._ex_prefix(e, '+')
 
     def _ex_prefix_minus(self, e):
-        if e.bracket(e[0]):
-            return '(-(' + self.ex(e[0]) + '))'
-        else:
-            return '(-' + self.ex(e[0]) + ')'
+        return self._ex_prefix(e, '-')
 
     def _ex_plus(self, e):
         return self._ex_infix(e, '+')
@@ -100,7 +105,17 @@ def _ex_remainder(self, e):
         return self._ex_infix(e, '%')
 
     def _ex_power(self, e):
-        return self._ex_infix(e, '**')
+        # Note: Python uses a right-to-left order of operations for power, so
+        # that a**b**c means a**(b**c). In Myokit, a^b^c means (a^b)^c, so we
+        # need to reverse this for powers only.
+        if e.bracket(e[0]) or isinstance(e[0], myokit.Power):
+            out = f'({self.ex(e[0])})'
+        else:
+            out = f'{self.ex(e[0])}'
+        if e.bracket(e[1]) and not isinstance(e[1], myokit.Power):
+            return f'{out}**({self.ex(e[1])})'
+        else:
+            return f'{out}**{self.ex(e[1])}'
 
     def _ex_sqrt(self, e):
         return self._ex_function(e, 'sqrt')
@@ -139,46 +154,44 @@ def _ex_ceil(self, e):
         return self._ex_function(e, 'ceil')
 
     def _ex_abs(self, e):
-        return 'abs(' + self.ex(e[0]) + ')'
-
-    def _ex_not(self, e):
-        return 'not (' + self.ex(e[0]) + ')'
+        return f'abs({self.ex(e[0])})'
 
     def _ex_equal(self, e):
-        return self._ex_infix_condition(e, '==')
+        return self._ex_infix_comparison(e, '==')
 
     def _ex_not_equal(self, e):
-        return self._ex_infix_condition(e, '!=')
+        return self._ex_infix_comparison(e, '!=')
 
     def _ex_more(self, e):
-        return self._ex_infix_condition(e, '>')
+        return self._ex_infix_comparison(e, '>')
 
     def _ex_less(self, e):
-        return self._ex_infix_condition(e, '<')
+        return self._ex_infix_comparison(e, '<')
 
     def _ex_more_equal(self, e):
-        return self._ex_infix_condition(e, '>=')
+        return self._ex_infix_comparison(e, '>=')
 
     def _ex_less_equal(self, e):
-        return self._ex_infix_condition(e, '<=')
+        return self._ex_infix_comparison(e, '<=')
 
     def _ex_and(self, e):
-        return self._ex_infix_condition(e, 'and')
+        return self._ex_infix_logical(e, 'and')
 
     def _ex_or(self, e):
-        return self._ex_infix_condition(e, 'or')
+        return self._ex_infix_logical(e, 'or')
+
+    def _ex_not(self, e):
+        return f'(not {self.ex(e[0])})'
 
     def _ex_if(self, e):
-        return '(' + self.ex(e._t) + ' if ' + self.ex(e._i) + ' else ' \
-                   + self.ex(e._e) + ')'
+        return f'({self.ex(e._t)} if {self.ex(e._i)} else {self.ex(e._e)})'
 
     def _ex_piecewise(self, e):
         s = ''
         n = len(e) // 2
         for i in range(0, n):
-            s += '(' + self.ex(e._e[i]) + ' if ' + self.ex(e._i[i]) + ' else '
-        s += self.ex(e._e[n])
-        s += ')' * n
+            s += f'({self.ex(e._e[i])} if {self.ex(e._i[i])} else '
+        s += self.ex(e._e[n]) + ')' * n
         return s
 
 
@@ -216,25 +229,43 @@ def _ex_acos(self, e):
 
     def _ex_atan(self, e):
         return self._ex_function(e, 'arctan')
+
     #def _ex_exp(self, e):
-    #def _ex_log(self, e):
+
+    def _ex_log(self, e):
+        if len(e) == 1:
+            return self._ex_function(e, 'log')
+        # Always add brackets here: The parent element will have been expecting
+        # a function (which never needs brackets) and so won't have added any.
+        return f'(numpy.log({self.ex(e[0])}) / numpy.log({self.ex(e[1])}))'
+
     #def _ex_log10(self, e):
     #def _ex_floor(self, e):
     #def _ex_ceil(self, e):
-    #def _ex_abs(self, e):
-    #def _ex_not(self, e):
+
+    def _ex_abs(self, e):
+        # Can't use default abs here, must be numpy.abs
+        return self._ex_function(e, 'abs')
+
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
     #def _ex_more(self, e):
     #def _ex_less(self, e):
     #def _ex_more_equal(self, e):
     #def _ex_less_equal(self, e):
-    #def _ex_and(self, e):
-    #def _ex_or(self, e):
+
+    def _ex_and(self, e):
+        return self._ex_function(e, 'logical_and')
+
+    def _ex_or(self, e):
+        return self._ex_function(e, 'logical_or')
+
+    def _ex_not(self, e):
+        return self._ex_function(e, 'logical_not')
 
     def _ex_if(self, e):
-        return self._function_prefix + 'select([' + self.ex(e._i) + '], [' \
-            + self.ex(e._t) + '], ' + self.ex(e._e) + ')'
+        return (f'{self._function_prefix}select('
+                f'[{self.ex(e._i)}], [{self.ex(e._t)}], {self.ex(e._e)})')
 
     def _ex_piecewise(self, e):
         n = len(e._i)
diff --git a/myokit/formats/stan/_ewriter.py b/myokit/formats/stan/_ewriter.py
index 95917bd8b..adbf7c33d 100644
--- a/myokit/formats/stan/_ewriter.py
+++ b/myokit/formats/stan/_ewriter.py
@@ -20,20 +20,6 @@ def __init__(self):
         super().__init__()
         self._function_prefix = ''
 
-        self._fcond = None
-        self.set_condition_function('ifthenelse')
-
-    def set_condition_function(self, func=None):
-        """
-        Sets a function name to use for if statements
-
-        By setting func to None you can revert back to the default behavior
-         (the ternary operator). Any other value will be interpreted as the
-         name of a function taking arguments (condition, value_if_true,
-         value_if_false).
-        """
-        self._fcond = func
-
     #def _ex_name(self, e):
     #def _ex_derivative(self, e):
     #def _ex_number(self, e):
@@ -49,11 +35,22 @@ def _ex_quotient(self, e):
         return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
 
     def _ex_remainder(self, e):
-        # fmod uses correct convention
-        return self._ex_function(e, 'fmod')
+        # Extra brackets needed: Minus has lower precedence than division.
+        i = myokit.Minus(e[0], myokit.Multiply(
+            e[1], myokit.Floor(myokit.Divide(e[0], e[1]))))
+        return f'({self.ex(i)})'
 
     def _ex_power(self, e):
-        return self._ex_infix(e, '^')
+        # Like Python (and unlike Myokit), Stan uses a right-associative power
+        # operator.
+        if e.bracket(e[0]) or isinstance(e[0], myokit.Power):
+            out = f'({self.ex(e[0])})'
+        else:
+            out = f'{self.ex(e[0])}'
+        if e.bracket(e[1]) and not isinstance(e[1], myokit.Power):
+            return f'{out}^({self.ex(e[1])})'
+        else:
+            return f'{out}^{self.ex(e[1])}'
 
     #def _ex_sqrt(self, e):
     #def _ex_sin(self, e):
@@ -65,22 +62,16 @@ def _ex_power(self, e):
     #def _ex_exp(self, e):
 
     def _ex_log(self, e):
-        if len(e) == 1:
-            return self._ex_function(e, 'log')
-        else:
-            return '(log(' + self.ex(e[0]) + ') / log(' + self.ex(e[1]) + '))'
+        if len(e) == 2:
+            return f'(log({self.ex(e[0])}) / log({self.ex(e[1])}))'
+        return f'log({self.ex(e[0])})'
 
     def _ex_log10(self, e):
-        return 'log10(' + self.ex(e[0]) + ')'
+        return f'log10({self.ex(e[0])})'
 
     #def _ex_floor(self, e):
     #def _ex_ceil(self, e):
-
-    def _ex_abs(self, e):
-        return self._ex_function(e, 'abs')
-
-    def _ex_not(self, e):
-        return '!(' + self.ex(e[0]) + ')'
+    #def _ex_abs(self, e):
 
     #def _ex_equal(self, e):
     #def _ex_not_equal(self, e):
@@ -90,21 +81,22 @@ def _ex_not(self, e):
     #def _ex_less_equal(self, e):
 
     def _ex_and(self, e):
-        return self._ex_infix_condition(e, '&&')
+        return self._ex_infix_logical(e, '&&')
 
     def _ex_or(self, e):
-        return self._ex_infix_condition(e, '||')
+        return self._ex_infix_logical(e, '||')
+
+    def _ex_not(self, e):
+        return f'(!{self.ex(e[0])})'
 
     def _ex_if(self, e):
-        return ('(' + self.ex(e._i) + ' ? ' + self.ex(e._t) + ' : ' +
-                self.ex(e._e) + ')')
+        return f'({self.ex(e._i)} ? {self.ex(e._t)} : {self.ex(e._e)})'
 
     def _ex_piecewise(self, e):
         s = []
-        n = len(e._i)
-        for i in range(0, n):
-            s.append('(%s ? %s : ' % (self.ex(e._i[i]), self.ex(e._e[i])))
-        s.append(self.ex(e._e[n]))
-        s.append(')' * n)
+        for _if, _then in zip(e._i, e._e):
+            s.append(f'({self.ex(_if)} ? {self.ex(_then)} : ')
+        s.append(self.ex(e._e[-1]))
+        s.append(')' * len(e._i))
         return ''.join(s)
 


Test patch:
diff --git a/myokit/tests/__init__.py b/myokit/tests/__init__.py
index b32bf88d5..102f8d048 100644
--- a/myokit/tests/__init__.py
+++ b/myokit/tests/__init__.py
@@ -10,6 +10,7 @@
 #
 import os
 import tempfile
+import unittest
 import warnings
 
 import myokit
@@ -256,3 +257,72 @@ def test_case_pk_model(parameters, times):
     partials = np.vstack([damount_dinitial_amount, damount_delimination_rate])
 
     return amount, partials
+
+
+class ExpressionWriterTestCase(unittest.TestCase):
+    """ Abstract class for expression writer tests. """
+
+    _name = None
+    _target = None
+    _update_lhs_function = True
+
+    @classmethod
+    def setUpClass(cls):
+        # Create a model with some variables for testing
+        cls.model = m = myokit.Model()
+        cls.component = c = m.add_component('comp')
+        cls.a = myokit.Name(c.add_variable('a', rhs=1))
+        cls.b = myokit.Name(c.add_variable('b', rhs=2))
+        cls.c = myokit.Name(c.add_variable('c', rhs=3))
+        cls.d = myokit.Name(c.add_variable('d', rhs=4))
+        cls.e = myokit.Name(c.add_variable('e', rhs=5))
+        cls.f = myokit.Name(c.add_variable('f', rhs=6))
+        cls.g = myokit.Name(c.add_variable('g', rhs=7))
+        cls.t = c.add_variable('t', rhs=0, binding='time')
+
+        # Set unames
+        m.validate()
+
+        # Create writer
+        cls.w = cls._target()
+        if cls._update_lhs_function:
+            cls.w.set_lhs_function(cls.lhs)
+
+        # Easy access to properties
+        cls.ab = (cls.a, cls.b)
+        cls.abc = (cls.a, cls.b, cls.c)
+        cls.abcd = (cls.a, cls.b, cls.c, cls.d)
+        cls.efg = (cls.e, cls.f, cls.g)
+
+    @classmethod
+    def lhs(cls, ex):
+        """
+        Easier to read LHS function: ignores components.
+
+        All LHS types are supported here: Lack of support for e.g. partial
+        derivatives should be implemented in the expression writers themselves.
+        """
+        if isinstance(ex, myokit.Name):
+            return ex.var().name()
+        elif isinstance(ex, myokit.Derivative):
+            return f'dot({ex.var().name()})'
+        elif isinstance(ex, myokit.InitialValue):
+            return f'initial({ex.var().name()})'
+        elif isinstance(ex, myokit.PartialDerivative):
+            v1 = ex.dependent_expression()
+            v2 = ex.independent_expression()
+            return f'partial({v1.var().name()}, {v2.var().name()})'
+        raise ValueError(f'Untested LHS type {type(ex)}')
+
+    def test_fetching(self):
+        # Test fetching using ewriter method
+        w = myokit.formats.ewriter(self._name)
+        self.assertIsInstance(w, self._target)
+
+    def test_bad_argument(self):
+        # Test without a Myokit expression
+        self.assertRaisesRegex(
+            ValueError, 'Unknown expression type', self.w.ex, 7)
+
+    def eq(self, expression, expected_output):
+        self.assertEqual(self.w.ex(expression), expected_output)
diff --git a/myokit/tests/data/decker.model b/myokit/tests/data/decker.model
index 9638cd378..6d77ac320 100644
--- a/myokit/tests/data/decker.model
+++ b/myokit/tests/data/decker.model
@@ -113,7 +113,7 @@ calcium_alpha = 0.05;
 calcium_beta = 0.00068;
 csqnBar = 10.0;
 diff_fTrap = calcium_alpha * active * (active - fTrap) - calcium_beta * fTrap;
-diff_uCa_cal = (-ICaL) * AF / (v_cal * 2.0) + CaL * v_sr / v_cal;
+diff_uCa_cal = -ICaL * AF / (v_cal * 2.0) + CaL * v_sr / v_cal;
 diff_uCa_i = Ca * v_sr / v_cyt - (ICab + IpCa - 2.0 * INaCa) * AF / (v_cyt * 2.0) - (Iup - Ileak) * v_nsr / v_cyt;
 diff_uCa_jsr = Itr - Irel;
 diff_uCa_sr = INaCaSS * AF / v_sr + Irel * v_jsr / v_sr - (Ca + CaL);
@@ -122,11 +122,11 @@ diff_uCa_sr = INaCaSS * AF / v_sr + Irel * v_jsr / v_sr - (Ca + CaL);
 BSLBar = 1.124;
 BSRBar = 0.047;
 BS_sum = BSRBar + BSLBar;
-Ca_cal = (-b_2) / 3.0 + 2.0 / 3.0 * sqrt(b_2 * b_2 - 3.0 * c_2) * cos(acos((9.0 * b_2 * c_2 - 2.0 * pow(b_2, 3.0) - 27.0 * d_1) / (2.0 * pow(b_2 * b_2 - 3.0 * c_2, 1.5))) / 3.0);
+Ca_cal = -b_2 / 3.0 + 2.0 / 3.0 * sqrt(b_2 * b_2 - 3.0 * c_2) * cos(acos((9.0 * b_2 * c_2 - 2.0 * pow(b_2, 3.0) - 27.0 * d_1) / (2.0 * pow(b_2 * b_2 - 3.0 * c_2, 1.5))) / 3.0);
 b_2 = sum2 - uCa_cal;
 c_2 = pro_sum2 - Km_sum2 * uCa_cal;
-d_1 = (-Km_pro2) * uCa_cal;
-Ca_i = (-b_3) / 3.0 + 2.0 / 3.0 * sqrt(b_3 * b_3 - 3.0 * c_3) * cos(acos((9.0 * b_3 * c_3 - 2.0 * pow(b_3, 3.0) - 27.0 * d_2) / (2.0 * pow(b_3 * b_3 - 3.0 * c_3, 1.5))) / 3.0);
+d_1 = -Km_pro2 * uCa_cal;
+Ca_i = -b_3 / 3.0 + 2.0 / 3.0 * sqrt(b_3 * b_3 - 3.0 * c_3) * cos(acos((9.0 * b_3 * c_3 - 2.0 * pow(b_3, 3.0) - 27.0 * d_2) / (2.0 * pow(b_3 * b_3 - 3.0 * c_3, 1.5))) / 3.0);
 Km_cmdn = 0.00238;
 Km_pro = Km_cmdn * Km_trpn;
 Km_sum = Km_trpn + Km_cmdn;
@@ -134,14 +134,14 @@ Km_trpn = 0.0005;
 b_3 = sum - uCa_i;
 c_3 = pro - uCa_i * Km_sum;
 cmdnBar = 0.05;
-d_2 = (-Km_pro) * uCa_i;
+d_2 = -Km_pro * uCa_i;
 pro = Km_pro + trpnBar * Km_cmdn + cmdnBar * Km_trpn;
 sum = cmdnBar + trpnBar + Km_trpn + Km_cmdn;
 trpnBar = 0.07;
-Ca_sr = (-b_4) / 3.0 + 2.0 / 3.0 * sqrt(b_4 * b_4 - 3.0 * c_4) * cos(acos((9.0 * b_4 * c_4 - 2.0 * pow(b_4, 3.0) - 27.0 * d_3) / (2.0 * pow(b_4 * b_4 - 3.0 * c_4, 1.5))) / 3.0);
+Ca_sr = -b_4 / 3.0 + 2.0 / 3.0 * sqrt(b_4 * b_4 - 3.0 * c_4) * cos(acos((9.0 * b_4 * c_4 - 2.0 * pow(b_4, 3.0) - 27.0 * d_3) / (2.0 * pow(b_4 * b_4 - 3.0 * c_4, 1.5))) / 3.0);
 b_4 = sum2 - uCa_sr;
 c_4 = pro_sum2 - Km_sum2 * uCa_sr;
-d_3 = (-Km_pro2) * uCa_sr;
+d_3 = -Km_pro2 * uCa_sr;
 Km_BSL = 0.0087;
 Km_BSR = 0.00087;
 Km_pro2 = Km_BSR * Km_BSL;
@@ -152,7 +152,7 @@ sum2 = BS_sum + Km_sum2;
 // cell
 A = 2.0 * geoArea;
 AF = A / F;
-geoArea = 3.14 * 2.0 * radius * radius + 2.0 *  3.14159265358979312e+00 * radius * length;
+geoArea = 3.14 * 2.0 * radius * radius + 2.0 * 3.14159265358979312e+00 * radius * length;
 length = 0.01;
 radius = 0.0011;
 v_cal = volume * 0.002;
@@ -181,9 +181,9 @@ z1_1 = EK - ECl;
 z2_1 = 87.8251;
 CTNaCl = NaClBar * z1_2 / (z1_2 + z2_2);
 z1_2 = pow(ENa - ECl, 4.0);
-z2_2 =  5.94941976406925395e+07;
+z2_2 = 5.94941976406925395e+07;
 KClBar = 1.77e-05;
-NaClBar =  2.46108000000000016e-05;
+NaClBar = 2.46108000000000016e-05;
 
 // diff
 Ca = (Ca_sr - Ca_i) / var_diff_tau_diff;
@@ -206,10 +206,10 @@ e2v_1 = exp(2.0 * vf);
 // ical
 ACT_ss = 1.0 / (1.0 + exp((p05_1 - V) / p06_1));
 ACT_tau = p00_1 + p01_1 * exp(p02_1 * (V + p03_1)) / (1.0 + exp(p04_1 * (V + p03_1)));
-diff_C = (-(ical_alpha + delta_1 + yy)) * C + ical_beta * O + theta * Cs + xx * CI;
-diff_CI = (-(ical_alpha + delta_I + xx)) * CI + yy * C + theta_I * CIs + ical_beta * OI;
-diff_CIs = (-(ical_alpha + theta_I + xs)) * CIs + ys * Cs + delta_I * CI + ical_beta * OIs;
-diff_Cs = (-(ical_alpha + theta + ys)) * Cs + delta_1 * C + ical_beta * Os + xs * CIs;
+diff_C = -(ical_alpha + delta_1 + yy) * C + ical_beta * O + theta * Cs + xx * CI;
+diff_CI = -(ical_alpha + delta_I + xx) * CI + yy * C + theta_I * CIs + ical_beta * OI;
+diff_CIs = -(ical_alpha + theta_I + xs) * CIs + ys * Cs + delta_I * CI + ical_beta * OIs;
+diff_Cs = -(ical_alpha + theta + ys) * Cs + delta_1 * C + ical_beta * Os + xs * CIs;
 ICaL = ICaLBar * g_1;
 ICaLBar = ((e2v_2 == 1.0) ? p21 * 4.0 * F * FRT * (gCai * Ca_cal - gCao * Cao) / (2.0 * FRT) : p21 * 4.0 * vff * (gCai * Ca_cal * e2v_2 - gCao * Cao) / (e2v_2 - 1.0));
 e2v_2 = exp(2.0 * vf);
@@ -217,16 +217,16 @@ g_1 = O + Os;
 gCai = 1.0;
 gCao = 0.341;
 I_V1 = p07_1 * (1.0 + exp((V + p08_1) / p09_1));
-I_V2 = p10_1 * (1.0 + exp((-(V + p11_1)) / p12_1));
+I_V2 = p10_1 * (1.0 + exp(-(V + p11_1) / p12_1));
 I_Vss = (p15_1 + 1.0 / (1.0 + exp((V + p13_1) / p14_1))) / (1.0 + p15_1);
 I_Vtau = 1.0 / (1.0 / I_V1 + 1.0 / I_V2);
 Is_Ca = p16 - p17 / (1.0 + zz_2);
-Is_V2 = Is_Ca * (1.0 + exp((-(V + p11_1)) / p12_1));
+Is_V2 = Is_Ca * (1.0 + exp(-(V + p11_1) / p12_1));
 Is_Vss = (p18 + 1.0 / (1.0 + exp((V + p13_1) / p14_1))) / (1.0 + p18);
 Is_Vtau = 1.0 / (1.0 / I_V1 + 1.0 / Is_V2);
-diff_O = (-(ical_beta + delta_1 + yy)) * O + ical_alpha * C + theta * Os + xx * OI;
-diff_OI = (-(ical_beta + delta_I + xx)) * OI + yy * O + theta_I * OIs + ical_alpha * CI;
-diff_OIs = (-(ical_beta + theta_I + xs)) * OIs + ys * Os + delta_I * OI + ical_alpha * CIs;
+diff_O = -(ical_beta + delta_1 + yy) * O + ical_alpha * C + theta * Os + xx * OI;
+diff_OI = -(ical_beta + delta_I + xx) * OI + yy * O + theta_I * OIs + ical_alpha * CI;
+diff_OIs = -(ical_beta + theta_I + xs) * OIs + ys * Os + delta_I * OI + ical_alpha * CIs;
 Os = 1.0 - C - CI - CIs - Cs - O - OI - OIs;
 ical_alpha = ACT_ss / ACT_tau;
 ical_beta = (1.0 - ACT_ss) / ACT_tau;
@@ -255,7 +255,7 @@ p17 = 17.5;
 p18 = 0.0001;
 p19 = 3.0;
 p20 = 1e-06;
-p21 =  1.55520000000000002e-04;
+p21 = 1.55520000000000002e-04;
 theta = 1.0;
 theta_I = p20;
 xs = Is_Vss / Is_Vtau;
@@ -274,7 +274,7 @@ Ibar_1 = ((evf == 1.0) ? 9e-07 * vff * Clo : 9e-07 * vff * (Cl_i - Clo * evf) /
 KCaI = 1.0 - 1.0 / (1.0 + pow(Irel, 2.0) / 0.16);
 diff_i_fast_1 = (ss - i_fast_1) / 8.0;
 i_fast_alpha = 0.025 / (1.0 + exp((V + 58.0) / 5.0));
-i_fast_beta = 1.0 / (5.0 * (1.0 + exp((V + 19.0) / (-9.0))));
+i_fast_beta = 1.0 / (5.0 * (1.0 + exp((V + 19.0) / -9.0)));
 ss = i_fast_alpha / (i_fast_alpha + i_fast_beta);
 
 // ik1
@@ -283,17 +283,17 @@ Gbar_2 = 0.5;
 IK1 = GK1 * K1 * dv;
 K1 = ak / (ak + bk);
 ak = 1.02 / (1.0 + exp(0.2385 * (dv - 59.215)));
-bk = (0.49124 * exp(0.08032 * (dv + 5.476)) + exp(0.06175 * (dv - 594.31))) / (1.0 + exp((-0.5143) * (dv + 4.753)));
+bk = (0.49124 * exp(0.08032 * (dv + 5.476)) + exp(0.06175 * (dv - 594.31))) / (1.0 + exp(-0.5143 * (dv + 4.753)));
 dv = V - EK;
 
 // ikr
 G = Gbar_3 * sqrt(Ko / 5.4);
 Gbar_3 = 0.0138542;
 IKr = G * a_1 * i_1 * (V - EK);
-a_1_inf = 1.0 / (1.0 + exp((V + 10.085) / (-4.25)));
+a_1_inf = 1.0 / (1.0 + exp((V + 10.085) / -4.25));
 tau_a_1 = 1.0 / (0.0006 * ta - 0.0003 * tb);
 ta = ((dd_1 == 0.0) ? 1.0 / 0.136 : vv_1 / dd_1);
-dd_1 = -expm1((-0.136) * vv_1);
+dd_1 = -expm1(-0.136 * vv_1);
 vv_1 = V - 1.7384;
 tb = ((dd_2 == 0.0) ? 1.0 / 0.1522 : vv_2 / dd_2);
 dd_2 = -expm1(0.1522 * vv_2);
@@ -318,7 +318,7 @@ diff_C8 = 2.0 * a_2 * C7 + 3.0 * b_5 * C9 + 3.0 * g_2 * C4 + 2.0 * d_4 * C11 - C
 diff_C9 = a_2 * C8 + 4.0 * g_2 * C5 + 2.0 * d_4 * C12 - C9 * (3.0 * b_5 + 3.0 * g_2 + d_4);
 GKs = p15_2 * (1.0 + 0.6 / (1.0 + pow(3.8e-05 / Ca_i, 1.4)));
 IKs = GKs * (O1 + O2) * (V - EKs);
-diff_O1 = (-(e + p)) * O1 + o * O2 + t * C15;
+diff_O1 = -(e + p) * O1 + o * O2 + t * C15;
 O2 = 1.0 - C1 - C2 - C3 - C4 - C5 - C6 - C7 - C8 - C9 - C10 - C11 - C12 - C13 - C14 - C15 - O1;
 a_2 = p00_2 * exp(vf * p01_2);
 b_5 = p02_2 * exp(vf * p03_2);
@@ -327,21 +327,21 @@ e = p08_2 * exp(vf * p09_2);
 g_2 = p04_2 * exp(vf * p05_2);
 o = p11_2 * exp(vf * p12_2);
 p = p13_2 * exp(vf * p14_2);
-p00_2 =  1.48645979808599994e-02;
-p01_2 =  2.98773012358800015e-02;
-p02_2 =  8.39863121998299933e-02;
-p03_2 = (- 5.54610571266399982e-02);
-p04_2 =  1.46006611831600002e-02;
-p05_2 =  2.44649530996450010e-01;
-p06_2 =  3.11732688739999996e-03;
-p07_2 = (- 4.26254519443760027e-01);
-p08_2 =  7.73199009733099990e-02;
-p09_2 = (- 6.47261224887100001e-02);
-p10_2 =  8.95383064110199939e-02;
-p11_2 =  7.94054599586400012e-01;
-p12_2 = (- 8.01737819297699966e-02);
-p13_2 =  5.86382286630140026e-01;
-p14_2 =  2.82055543314960022e-01;
+p00_2 = 1.48645979808599994e-02;
+p01_2 = 2.98773012358800015e-02;
+p02_2 = 8.39863121998299933e-02;
+p03_2 = -5.54610571266399982e-02;
+p04_2 = 1.46006611831600002e-02;
+p05_2 = 2.44649530996450010e-01;
+p06_2 = 3.11732688739999996e-03;
+p07_2 = -4.26254519443760027e-01;
+p08_2 = 7.73199009733099990e-02;
+p09_2 = -6.47261224887100001e-02;
+p10_2 = 8.95383064110199939e-02;
+p11_2 = 7.94054599586400012e-01;
+p12_2 = -8.01737819297699966e-02;
+p13_2 = 5.86382286630140026e-01;
+p14_2 = 2.82055543314960022e-01;
 p15_2 = 0.0826;
 t = p10_2;
 
@@ -353,14 +353,14 @@ K = 1.0 / (1.0 + exp((7.488 - V) / 5.98));
 // ina
 Gbar_5 = 9.075;
 INa = Gbar_5 * pow(m, 3.0) * h * j * (V - ENa);
-alpha_h = ((V >= (-40.0)) ? 0.0 : 0.135 * exp((80.0 + V) / (-6.8)));
-beta_h = ((V >= (-40.0)) ? 1.0 / (0.13 * (1.0 + exp((V + 10.66) / (-11.1)))) : 3.56 * exp(0.079 * V) + 310000.0 * exp(0.35 * V));
-alpha_j = ((V >= (-40.0)) ? 0.0 : ((-127140.0) * exp(0.2444 * V) - 6.948e-05 * exp((-0.04391) * V)) * (V + 37.78) / (1.0 + exp(0.311 * (V + 79.23))));
-beta_j = ((V >= (-40.0)) ? 0.3 * exp((-2.535e-07) * V) / (1.0 + exp((-0.1) * (V + 32.0))) : 0.1212 * exp((-0.01052) * V) / (1.0 + exp((-0.1378) * (V + 40.14))));
+alpha_h = ((V >= -40.0) ? 0.0 : 0.135 * exp((80.0 + V) / -6.8));
+beta_h = ((V >= -40.0) ? 1.0 / (0.13 * (1.0 + exp((V + 10.66) / -11.1))) : 3.56 * exp(0.079 * V) + 310000.0 * exp(0.35 * V));
+alpha_j = ((V >= -40.0) ? 0.0 : (-127140.0 * exp(0.2444 * V) - 6.948e-05 * exp(-0.04391 * V)) * (V + 37.78) / (1.0 + exp(0.311 * (V + 79.23))));
+beta_j = ((V >= -40.0) ? 0.3 * exp(-2.535e-07 * V) / (1.0 + exp(-0.1 * (V + 32.0))) : 0.1212 * exp(-0.01052 * V) / (1.0 + exp(-0.1378 * (V + 40.14))));
 alpha_m = ((dd_3 == 0.0) ? 3.2 : 0.32 * vv_3 / dd_3);
-dd_3 = -expm1((-0.1) * vv_3);
+dd_3 = -expm1(-0.1 * vv_3);
 vv_3 = V + 47.13;
-beta_m = 0.08 * exp((-V) / 11.0);
+beta_m = 0.08 * exp(-V / 11.0);
 
 // inab
 INaB = ((evf == 1.0) ? P * F * (Nai - Nao) : P * vff * (Nai * evf - Nao) / (evf - 1.0));
@@ -398,21 +398,21 @@ vMax = 4.5;
 // inak
 INaK = Ibar_2 * fv * pow(Pna, 3.0) * Pk;
 Pna = Nai / (Nai + K_Nai);
-fv = 1.0 / (1.0 + exp((-phi)));
+fv = 1.0 / (1.0 + exp(-phi));
 phi = (V - VHalf) * FRT;
 Ibar_2 = 1.4;
 K_Ko = 1.5;
 K_Nai = 2.6;
 Pk = Ko / (Ko + K_Ko);
-VHalf = (-92.0);
+VHalf = -92.0;
 
 // inal
 Gbar_6 = 0.0065;
 INaL = Gbar_6 * pow(a_3, 3.0) * i_2 * (V - ENa);
 alpha_a_3 = ((dd_4 == 0.0) ? 3.2 : 0.32 * vv_4 / dd_4);
-dd_4 = -expm1((-0.1) * vv_4);
+dd_4 = -expm1(-0.1 * vv_4);
 vv_4 = V + 47.13;
-beta_a_3 = 0.08 * exp((-V) / 11.0);
+beta_a_3 = 0.08 * exp(-V / 11.0);
 diff_i_2 = (hLss - i_2) / 600.0;
 hLss = 1.0 / (1.0 + exp((V + 91.0) / 6.1));
 
@@ -422,7 +422,7 @@ IpCa_bar = 0.0575;
 Km_pCa = 0.0005;
 
 // irel
-diff_Irel = (-(irel_inf_var + Irel)) / irel_tau;
+diff_Irel = -(irel_inf_var + Irel) / irel_tau;
 irel_alpha = irel_beta * 0.1125;
 irel_beta = 4.75 * (1.0 + delta_2);
 delta_2 = 1.0 / (1.0 + pow(0.28 / active, 10.0));
@@ -433,11 +433,11 @@ irel_tau = irel_beta / (1.0 + 0.0123 / Ca_jsr);
 Gbar_7 = 0.4975;
 ITo = Gbar_7 * pow(a_4, 3.0) * i_fast_2 * i_slow * Rto1 * (V - EK);
 Rto1 = exp(V / 550.0);
-ito_a_alpha = 1.0 / (1.2089 * (1.0 + exp((V - 18.4099) / (-29.3814))));
+ito_a_alpha = 1.0 / (1.2089 * (1.0 + exp((V - 18.4099) / -29.3814)));
 ito_a_beta = 3.5 / (1.0 + exp((V + 100.0) / 29.3814));
-a_4_inf = 1.0 / (1.0 + exp((V + 9.437) / (-7.133)));
+a_4_inf = 1.0 / (1.0 + exp((V + 9.437) / -7.133));
 tau_a_4 = 1.0 / (ito_a_alpha + ito_a_beta);
-ito_beta_i = 1.0 / (9.7953 * (1.0 + exp((V + 19.0) / (-9.0))));
+ito_beta_i = 1.0 / (9.7953 * (1.0 + exp((V + 19.0) / -9.0)));
 beta_i_fast_2 = ito_beta_i;
 beta_i_slow = ito_beta_i;
 alpha_i_fast_2 = 0.025 / (1.0 + exp((V + 58.0) / 5.0));
@@ -446,10 +446,10 @@ alpha_i_slow = 1.0 / (250.0 * (1.0 + exp((V + 60.0) / 5.0)));
 // membrane
 i_ion = INa_tot + IK_tot + ICa_tot + ICl_tot;
 i_stim = 0.0 * stim_amplitude;
-stim_amplitude = (-80.0);
+stim_amplitude = -80.0;
 
 // nernst
-ECl = (-RTF) * log(Clo / Cl_i);
+ECl = -RTF * log(Clo / Cl_i);
 EK = RTF * log(Ko / K_i);
 EKs = RTF * log((Ko + P_NaK * Nao) / (K_i + P_NaK * Nai));
 ENa = RTF * log(Nao / Nai);
@@ -464,14 +464,14 @@ T = 310.0;
 
 // potassium
 IK_tot = IK1 + IKr + IKs + IKur + ITo - INaK * 2.0;
-diff_K_i = (-IK_tot) * (AF / v_cyt) + CTKCl;
+diff_K_i = -IK_tot * (AF / v_cyt) + CTKCl;
 
 // sodium
 INa_cyt = INa + INaL + INaB + INaK * 3.0 + INaCa * 3.0;
 INa_tot = INa_cyt + INaCaSS * 3.0;
-diff_Na_sr = (-(3.0 * INaCaSS * r1_3 + Na));
+diff_Na_sr = -(3.0 * INaCaSS * r1_3 + Na);
 r1_3 = AF / v_sr;
-diff_Nai = (-r1_4) * INa_cyt + r2_2 * Na + CTNaCl;
+diff_Nai = -r1_4 * INa_cyt + r2_2 * Na + CTNaCl;
 r1_4 = AF / v_cyt;
 r2_2 = v_sr / v_cyt;
 
diff --git a/myokit/tests/test_formats.py b/myokit/tests/test_formats.py
index 670a9c3fa..07db35523 100755
--- a/myokit/tests/test_formats.py
+++ b/myokit/tests/test_formats.py
@@ -5,6 +5,7 @@
 # This file is part of Myokit.
 # See http://myokit.org for copyright, sharing, and licensing details.
 #
+import os
 import unittest
 
 import myokit
@@ -14,9 +15,7 @@
 
 
 class FormatsTest(unittest.TestCase):
-    """
-    Test infrastructure from the formats module.
-    """
+    """ Test shared formats functionality. """
 
     def test_register_external_importer(self):
         # Tests registering an external importer.
@@ -101,10 +100,8 @@ class TestExpressionWriter(myokit.formats.ExpressionWriter):
 
 
 class ExporterTest(unittest.TestCase):
-    """
-    Tests some parts of :class:`Exporter` that are not tested when testing the
-    various exporter implementations.
-    """
+    """ Tests shared :class:`Exporter` functionality. """
+
     def test_writable_dir(self):
         # Test :meth:`_test_writable_dir` for existing paths.
 
@@ -126,6 +123,117 @@ def test_writable_dir(self):
             self.assertRaisesRegex(
                 myokit.ExportError, 'file exists', e.runnable, path, m, p)
 
+    def test_runnable_exporter(self):
+        # Test shared functionality of the TemplatedRunnableExporters.
+
+        e = myokit.formats.exporter('ansic')
+
+        # Load model, protocol
+        m, p, x = myokit.load('example')
+
+        # Create empty output directory as subdirectory of DIR_OUT
+        with TemporaryDirectory() as d:
+            path = d.path()
+
+            # Simple export
+            dpath = os.path.join(path, 'runnable1')
+            ret = e.runnable(dpath, m)
+            self.assertIsNone(ret)
+            self.assertTrue(os.path.isdir(dpath))
+            self.assertTrue(len(os.listdir(dpath)) > 0)
+
+            # Write to complex path
+            dpath = os.path.join(path, 'runnable2', 'nest', 'test')
+            ret = e.runnable(dpath, m, p)
+            self.assertIsNone(ret)
+            self.assertTrue(os.path.isdir(dpath))
+            self.assertTrue(len(os.listdir(dpath)) > 0)
+
+            # Overwrite existing path
+            ret = e.runnable(dpath, m, p)
+            self.assertIsNone(ret)
+            self.assertTrue(os.path.isdir(dpath))
+            self.assertTrue(len(os.listdir(dpath)) > 0)
+
+            # Path pointing to file
+            dpath = os.path.join(path, 'file')
+            with open(dpath, 'w') as f:
+                f.write('contents\n')
+            self.assertRaisesRegex(
+                myokit.ExportError, 'file exists', e.runnable, dpath, m, p)
+
+            # Directory exists where we're trying to write a file
+            dpath = os.path.join(path, 'runnable3')
+            fname = os.path.join(dpath, 'sim.c')
+            os.makedirs(fname)
+            self.assertRaisesRegex(
+                myokit.ExportError, 'Directory exists',
+                e.runnable, dpath, m, p)
+
+            # Directory embedded in the output file path
+            def embedded():
+                return {'sim.c': 'nested/sim.c'}
+
+            # 1. Normal operation
+            e._dict = embedded
+            dpath = os.path.join(path, 'runnable4')
+            ret = e.runnable(dpath, m, p)
+            self.assertIsNone(ret)
+            self.assertTrue(os.path.isdir(dpath))
+            self.assertTrue(len(os.listdir(dpath)) > 0)
+
+            # 2. Try to create directory where file exists
+            def embedded():
+                return {'sim.c': 'nested/sim.c/som.c'}
+
+            e._dict = embedded
+            dpath = os.path.join(path, 'runnable4')
+            self.assertRaisesRegex(
+                myokit.ExportError, 'file or link', e.runnable, dpath, m, p)
+
+
+class ImporterTest(unittest.TestCase):
+    """ Test shared importer functionality. """
+
+    def test_importer_interface(self):
+        # Test listing and creating importers.
+        ims = myokit.formats.importers()
+        self.assertTrue(len(ims) > 0)
+        for i in ims:
+            self.assertIsInstance(i, str)
+            i = myokit.formats.importer(i)
+            self.assertTrue(isinstance(i, myokit.formats.Importer))
+
+    def test_unknown(self):
+        # Test requesting an unknown importer.
+        # Test fetching using importer method
+        self.assertRaisesRegex(
+            KeyError, 'Importer not found', myokit.formats.importer, 'blip')
+
+
+class EWriterTest(unittest.TestCase):
+    """ Test shared ewriter functionality. """
+
+    def test_ewriter_interface(self):
+        # Test listing and creating expression writers.
+
+        # Test listing
+        es = myokit.formats.ewriters()
+        self.assertTrue(len(es) > 0)
+
+        # Create one of each
+        for e in es:
+            self.assertIsInstance(e, str)
+            e = myokit.formats.ewriter(e)
+            self.assertTrue(isinstance(e, myokit.formats.ExpressionWriter))
+
+    def test_unknown(self):
+        # Test requesting an unknown expression writer.
+        # Test fetching using ewriter method
+        self.assertRaisesRegex(
+            KeyError, 'Expression writer not found', myokit.formats.ewriter,
+            'dada')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/myokit/tests/test_formats_ansic.py b/myokit/tests/test_formats_ansic.py
new file mode 100755
index 000000000..2fca66265
--- /dev/null
+++ b/myokit/tests/test_formats_ansic.py
@@ -0,0 +1,344 @@
+#!/usr/bin/env python3
+#
+# Tests writing of AnsiC expressions, as used by the Simulation.
+# C++ tests are lumped in as well.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import math
+import unittest
+
+import myokit
+import myokit.formats.ansic
+import myokit.formats.cpp
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
+
+import myokit.tests
+
+
+class AnsiCExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test conversion to Ansi C, as used by the Simulation.
+    Numerical tests are provided by composing and evaluating a single RHS.
+    """
+    _name = 'ansic'
+    _target = myokit.formats.ansic.AnsiCExpressionWriter
+
+    def test_number(self):
+        self.eq(Number(1), '1.0')
+        self.eq(Number(-2), '-2.0')
+        self.eq(Number(13, 'mV'), '13.0')
+
+    def test_name(self):
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        self.eq(myokit.PartialDerivative(self.a, self.b), 'partial(a, b)')
+
+    def test_initial_value(self):
+        self.eq(myokit.InitialValue(self.a), 'initial(a)')
+
+    def test_prefix_plus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0')
+        self.eq(PrefixPlus(PrefixPlus(p)), '+(+11.0)')
+        self.eq(PrefixPlus(PrefixPlus(PrefixPlus(p))), '+(+(+11.0))')
+        self.eq(PrefixPlus(Number('+1')), '+1.0')
+
+        # Test with operators of precedence SUM, PRODUCT
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), '+(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(PrefixPlus(Divide(b, a)), '+(b / a)')
+
+    def test_prefix_minus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixMinus(p), '-11.0')
+        self.eq(PrefixMinus(PrefixMinus(p)), '-(-11.0)')
+        self.eq(PrefixMinus(Number(-1)), '-(-1.0)')
+        self.eq(PrefixMinus(PrefixMinus(Number(-2))), '-(-(-2.0))')
+
+        # Test with operators of precedence SUM, PRODUCT
+        a, b, c = self.abc
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(PrefixMinus(Divide(b, a)), '-(b / a)')
+
+    def test_plus_minus(self):
+        a, b, c = self.abc
+        self.eq(Plus(a, b), 'a + b')
+        self.eq(Plus(Plus(a, b), c), 'a + b + c')
+        self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Minus(Minus(a, b), c), 'a - b - c')
+        self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Plus(Minus(a, b), c), 'a - b + c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+
+    def test_multiply_divide(self):
+        a, b, c = self.abc
+        self.eq(Multiply(a, b), 'a * b')
+        self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
+        self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
+
+        self.eq(Divide(a, b), 'a / b')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+
+        self.eq(Divide(Multiply(a, b), c), 'a * b / c')
+        self.eq(Multiply(Divide(a, b), c), 'a / b * c')
+        self.eq(Divide(a, Multiply(b, c)), 'a / (b * c)')
+        self.eq(Multiply(a, Divide(b, c)), 'a * (b / c)')
+
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Multiply(a, Plus(b, c)), 'a * (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Plus(a, Multiply(b, c)), 'a + b * c')
+        self.eq(Divide(Plus(a, b), c), '(a + b) / c')
+        self.eq(Divide(a, Minus(b, c)), 'a / (b - c)')
+        self.eq(Plus(Divide(a, b), c), 'a / b + c')
+        self.eq(Minus(a, Divide(b, c)), 'a - b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+
+    def test_quotient(self):
+        a, b, c = self.abc
+        self.eq(Quotient(a, b), 'floor(a / b)')
+        self.eq(Quotient(Plus(a, c), b), 'floor((a + c) / b)')
+        self.eq(Quotient(Divide(a, c), b), 'floor(a / c / b)')
+        self.eq(Quotient(a, Divide(b, c)), 'floor(a / (b / c))')
+        self.eq(Multiply(Quotient(a, b), c), 'floor(a / b) * c')
+        # Bracket() method expects a PRODUCT level operation, so will add
+        # unnecessary brackets here
+        self.eq(Multiply(c, Quotient(a, b)), 'c * (floor(a / b))')
+
+    def test_remainder(self):
+        a, b, c = self.abc
+        self.eq(Remainder(a, b), '(a - b * floor(a / b))')
+        self.eq(Remainder(Plus(a, c), b), '(a + c - b * floor((a + c) / b))')
+        self.eq(Multiply(Remainder(a, b), c), '(a - b * floor(a / b)) * c')
+        # Bracket() method expects a PRODUCT level operation, so will add
+        # unnecessary brackets here
+        self.eq(Divide(c, Remainder(b, a)), 'c / ((b - a * floor(b / a)))')
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'pow(a, b)')
+        self.eq(Power(Power(a, b), c), 'pow(pow(a, b), c)')
+        self.eq(Power(a, Power(b, c)), 'pow(a, pow(b, c))')
+
+        self.eq(Power(Plus(a, b), c), 'pow(a + b, c)')
+        self.eq(Power(a, Minus(b, c)), 'pow(a, b - c)')
+        self.eq(Power(Multiply(a, b), c), 'pow(a * b, c)')
+        self.eq(Power(a, Divide(b, c)), 'pow(a, b / c)')
+
+    def test_log(self):
+        a, b = self.ab
+        self.eq(Log(a), 'log(a)')
+        self.eq(Log10(a), 'log10(a)')
+        self.eq(Log(a, b), '(log(a) / log(b))')
+
+    def test_functions(self):
+        a, b = self.ab
+
+        self.eq(Sqrt(a), 'sqrt(a)')
+        self.eq(Exp(a), 'exp(a)')
+        self.eq(Sin(a), 'sin(a)')
+        self.eq(Cos(a), 'cos(a)')
+        self.eq(Tan(a), 'tan(a)')
+        self.eq(ASin(a), 'asin(a)')
+        self.eq(ACos(a), 'acos(a)')
+        self.eq(ATan(a), 'atan(a)')
+        self.eq(Floor(a), 'floor(a)')
+        self.eq(Ceil(a), 'ceil(a)')
+        self.eq(Abs(a), 'fabs(a)')
+
+    def test_conditions(self):
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), '(a && b)')
+        self.eq(Or(d, c), '(d || c)')
+        self.eq(Not(And(a, b)), '(!(a && b))')
+        self.eq(Not(c), '(!(c))')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)), '((d > c) || (b < a))')
+        self.eq(Not(Or(Number(1), Number(2))), '(!(1.0 || 2.0))')
+        self.eq(Not(Less(Number(1), Number(2))), '(!(1.0 < 2.0))')
+        self.eq(Not(Plus(Number(1), Number(2))), '(!(1.0 + 2.0))')
+
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0 == 0.0) == 0.0)')
+
+    def test_conditionals(self):
+
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), '((a == b) ? d : c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a), '((d != c) ? b : a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                '((a == b) ? c : ((a == d) ? 3.0 : 4.0))')
+
+        # Extra parentheses if condition is not a condition
+        self.eq(If(a, d, c), '((a) ? d : c)')
+        self.eq(Piecewise(a, b, c, d, Number(4)),
+                '((a) ? b : ((c) ? d : 4.0))')
+
+        # Using if-then-else function
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().name())
+        w.set_condition_function('ite')
+
+        self.assertEqual(w.ex(If(More(b, a), c, d)), 'ite((b > a), c, d)')
+        self.assertEqual(w.ex(Piecewise(Less(d, c), b, a)),
+                         'ite((d < c), b, a)')
+        self.assertEqual(w.ex(
+            Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4))),
+            'ite((a == b), c, ite((a == d), 3.0, 4.0))')
+
+    def test_in_c(self):
+        """ Compile and test the values evaluated in C. """
+
+        class CTester():
+            def __init__(self, parent):
+                self._parent = parent
+                self._m = myokit.Model()
+                self._c = self._m.add_component('c')
+                t = self._c.add_variable('time', rhs=0, binding='time')
+                self._expected = []
+
+            def add(self, expression, expected):
+                v = self._c.add_variable(
+                    f'v{len(self._expected)}', rhs=expression, initial_value=0)
+                self._expected.append(float(expected))
+
+            def run(self):
+                s = myokit.Simulation(self._m)
+                x = s.evaluate_derivatives()
+                self._parent.assertEqual(len(x), len(self._expected))
+                for a, b in zip(x, self._expected):
+                    self._parent.assertEqual(a, b)
+
+        c = CTester(self,)
+
+        c.add(Number(12, 'pF'), 12)
+
+        c.add(PrefixPlus(Number(3)), 3)
+        c.add(PrefixPlus(PrefixPlus(PrefixPlus(Number(4)))), 4)
+        c.add(PrefixMinus(Number(6)), -6)
+        c.add(PrefixMinus(PrefixMinus(Number(2))), 2)
+        c.add(PrefixMinus(PrefixMinus(PrefixMinus(Number(5)))), -5)
+        c.add(PrefixMinus(PrefixMinus(PrefixMinus(Number(-5)))), 5)
+
+        c.add(Plus(Number(4), Number(2)), 6)
+        c.add(Minus(Number(5), Number(1.5)), 3.5)
+
+        c.add(Multiply(Number(7), Number(9)), 63)
+        c.add(Divide(Number(5), Number(2)), 2.5)
+        c.add(Divide(Divide(Number(12), Number(2)), Number(2)), 3)
+        c.add(Divide(Number(1), Divide(Number(2), Number(3))), 1.5)
+
+        c.add(Remainder(Number(10), Number(4)), 2)
+        c.add(Remainder(Number(10), Number(6)), 4)
+        c.add(Remainder(Number(5), Number(3)), 2)
+        c.add(Remainder(Number(-5), Number(3)), 1)
+        c.add(Remainder(Number(5), Number(-3)), -1)
+        c.add(Remainder(Number(-5), Number(-3)), -2)
+
+        c.add(Quotient(Number(10), Number(4)), 2)
+        c.add(Quotient(Number(10), Number(6)), 1)
+        c.add(Quotient(Number(5), Number(3)), 1)
+        c.add(Quotient(Number(-5), Number(3)), -2)
+        c.add(Quotient(Number(5), Number(-3)), -2)
+        c.add(Quotient(Number(-5), Number(-3)), 1)
+
+        c.add(Power(Number(4), Power(Number(2), Number(3))), 65536)
+        c.add(Power(Power(Number(2), Number(3)), Number(4)), 4096)
+
+        c.add(Log(Number(3)), math.log(3))
+        c.add(Log10(Number(1000)), 3)
+        c.add(Log10(Number(0.01)), -2)
+        c.add(Log(Number(27), Number(3)), 3)
+        c.add(Divide(Number(12), Log(Number(256), Number(4))), 3)
+        c.add(Divide(Log(Number(256), Number(4)), Number(4)), 1)
+
+        c.add(Sqrt(Number(9)), 3)
+        c.add(Exp(Number(3)), math.exp(3))
+        c.add(Sin(Number(1)), math.sin(1))
+        c.add(Cos(Number(1)), math.cos(1))
+        c.add(Tan(Number(4)), math.tan(4))
+        c.add(ASin(Number(0.4)), math.asin(0.4))
+        c.add(ACos(Number(0.4)), math.acos(0.4))
+        c.add(ATan(Number(0.4)), math.atan(0.4))
+
+        c.add(Floor(Number(3.9)), 3)
+        c.add(Floor(Number(-3.9)), -4)
+        c.add(Ceil(Number(4.01)), 5)
+        c.add(Ceil(Number(-4.01)), -4)
+        c.add(Abs(PrefixMinus(Number(12))), 12)
+        c.add(Abs(Number(-13)), 13)
+
+        true = Equal(Number(2), Number(2))
+        false = Equal(Number(3), Number(-1))
+        a, b = Number(10), Number(20)
+        c.add(If(true, a, b), 10)
+        c.add(If(false, a, b), 20)
+
+        c.add(If(More(Number(5), Number(3)), a, b), 10)
+        c.add(If(More(Number(3), Number(3)), a, b), 20)
+        c.add(If(MoreEqual(Number(3), Number(3)), a, b), 10)
+        c.add(If(Less(Number(3), Number(5)), a, b), 10)
+        c.add(If(More(Number(3), Number(3)), a, b), 20)
+        c.add(If(LessEqual(Number(3), Number(3)), a, b), 10)
+
+        c.add(If(And(false, false), a, b), 20)
+        c.add(If(And(false, true), a, b), 20)
+        c.add(If(And(true, false), a, b), 20)
+        c.add(If(And(true, true), a, b), 10)
+        c.add(If(Or(false, false), a, b), 20)
+        c.add(If(Or(false, true), a, b), 10)
+        c.add(If(Or(true, false), a, b), 10)
+        c.add(If(Or(true, true), a, b), 10)
+        c.add(If(Not(true), a, b), 20)
+        c.add(If(Not(false), a, b), 10)
+
+        c.add(If(Equal(Equal(Number(0), Number(0)), Number(0)), a, b), 20)
+        c.add(If(Equal(Equal(Number(0), Number(0)), Number(1)), a, b), 10)
+
+        c.add(Piecewise(true, Number(10), false, Number(20), Number(30)), 10)
+        c.add(Piecewise(true, Number(10), true, Number(20), Number(30)), 10)
+        c.add(Piecewise(false, Number(10), true, Number(20), Number(30)), 20)
+        c.add(Piecewise(false, Number(10), false, Number(20), Number(30)), 30)
+
+        c.run()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_axon.py b/myokit/tests/test_formats_axon.py
index ad35785a9..c5bd98974 100755
--- a/myokit/tests/test_formats_axon.py
+++ b/myokit/tests/test_formats_axon.py
@@ -11,6 +11,7 @@
 import numpy as np
 
 import myokit
+import myokit.formats
 import myokit.formats.axon as axon
 
 from myokit.tests import TemporaryDirectory, DIR_FORMATS, WarningCollector
@@ -853,5 +854,21 @@ def test_accessors(self):
             self.assertEqual(atf.version(), '1.0')
 
 
+class AbfImporterInterfaceTest(unittest.TestCase):
+    """ Tests ABF importer interface. """
+
+    def test_capability_reporting(self):
+        # Test if the right capabilities are reported.
+        i = myokit.formats.importer('abf')
+        self.assertFalse(i.supports_component())
+        self.assertFalse(i.supports_model())
+        self.assertTrue(i.supports_protocol())
+
+    def test_protocol(self):
+        i = myokit.formats.importer('abf')
+        self.assertTrue(i.supports_protocol())
+        i.protocol(os.path.join(DIR_FORMATS, 'abf-v1.abf'))
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/myokit/tests/test_formats_cpp.py b/myokit/tests/test_formats_cpp.py
new file mode 100755
index 000000000..b654055fb
--- /dev/null
+++ b/myokit/tests/test_formats_cpp.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+#
+# Tests the expression writer for C++.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import unittest
+
+import myokit
+import myokit.formats.cpp
+
+import myokit.tests
+
+
+class CppExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test the C++ expression writer.
+    This inherits from Ansi C, only dropping support for initial values and
+    partial derivatives.
+    """
+    _name = 'cpp'
+    _target = myokit.formats.cpp.CppExpressionWriter
+
+    def test_basics(self):
+        # Test a few arbitrary expressions, just to check the inheritcance has
+        # worked.
+        a, b, c, d = self.abcd
+        self.eq(myokit.Number(1), '1.0')
+        self.eq(myokit.Number(13, 'mV'), '13.0')
+        self.eq(myokit.Divide(myokit.PrefixPlus(myokit.Plus(a, b)), c),
+                '+(a + b) / c')
+        self.eq(myokit.Multiply(myokit.PrefixMinus(myokit.Plus(b, a)), c),
+                '-(b + a) * c')
+        self.eq(myokit.Multiply(myokit.Divide(a, b), c), 'a / b * c')
+        self.eq(myokit.Divide(a, myokit.Multiply(b, c)), 'a / (b * c)')
+        self.eq(myokit.Quotient(myokit.Divide(a, c), b), 'floor(a / c / b)')
+        self.eq(myokit.ASin(a), 'asin(a)')
+        self.eq(myokit.Power(myokit.PrefixMinus(a), b), 'pow(-a, b)')
+        self.eq(myokit.Power(a, myokit.Minus(b, c)), 'pow(a, b - c)')
+        self.eq(myokit.Power(myokit.Multiply(a, b), c), 'pow(a * b, c)')
+        self.eq(myokit.Log(a, b), '(log(a) / log(b))')
+        self.eq(myokit.Sin(a), 'sin(a)')
+        self.eq(myokit.And(myokit.Equal(a, b), myokit.NotEqual(c, d)),
+                '((a == b) && (c != d))')
+        self.eq(myokit.Not(myokit.Less(myokit.Number(1), myokit.Number(2))),
+                '(!(1.0 < 2.0))')
+        self.eq(myokit.If(myokit.Equal(a, b), d, c), '((a == b) ? d : c)')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        self.assertRaisesRegex(
+            NotImplementedError, 'Partial',
+            self.w.ex, myokit.PartialDerivative(self.a, self.b))
+
+    def test_initial_value(self):
+        self.assertRaisesRegex(
+            NotImplementedError, 'Initial',
+            self.w.ex, myokit.InitialValue(self.a))
+
+    def test_conditionals(self):
+        # Inherited from AnsiCExpressionWriter
+
+        a, b, c, d = self.abcd
+        self.eq(myokit.If(myokit.Equal(a, b), d, c), '((a == b) ? d : c)')
+        self.eq(myokit.Piecewise(myokit.NotEqual(d, c), b, a),
+                '((d != c) ? b : a)')
+        self.eq(myokit.Piecewise(myokit.Equal(a, b), c,
+                                 myokit.Equal(a, d), myokit.Number(3),
+                                 myokit.Number(4)),
+                '((a == b) ? c : ((a == d) ? 3.0 : 4.0))')
+
+        # Extra parentheses if condition is not a condition
+        self.eq(myokit.If(a, d, c), '((a) ? d : c)')
+        self.eq(myokit.Piecewise(a, b, c, d, myokit.Number(4)),
+                '((a) ? b : ((c) ? d : 4.0))')
+
+        # Using if-then-else function
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().name())
+        w.set_condition_function('ite')
+
+        self.assertEqual(w.ex(myokit.If(myokit.More(b, a), c, d)),
+                         'ite((b > a), c, d)')
+        self.assertEqual(w.ex(myokit.Piecewise(myokit.Less(d, c), b, a)),
+                         'ite((d < c), b, a)')
+        self.assertEqual(
+            w.ex(myokit.Piecewise(myokit.Equal(a, b), c,
+                                  myokit.Equal(a, d), myokit.Number(3),
+                                  myokit.Number(4))),
+            'ite((a == b), c, ite((a == d), 3.0, 4.0))')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_cuda.py b/myokit/tests/test_formats_cuda.py
new file mode 100755
index 000000000..5bb95531e
--- /dev/null
+++ b/myokit/tests/test_formats_cuda.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python3
+#
+# Tests the expression writer for CUDA.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import unittest
+
+import myokit
+import myokit.formats.cuda
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
+
+import myokit.tests
+
+
+class CudaExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test conversion to Ansi C, as used by the Simulation.
+    Numerical tests are provided by composing and evaluating a single RHS.
+    """
+    _name = 'cuda'
+    _target = myokit.formats.cuda.CudaExpressionWriter
+
+    def test_number(self):
+        self.eq(Number(1), '1.0f')
+        self.eq(Number(-2), '-2.0f')
+        self.eq(Number(13, 'mV'), '13.0f')
+
+    def test_number_double(self):
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        self.assertEqual(w.ex(Number(1)), '1.0')
+        self.assertEqual(w.ex(Number(-2)), '-2.0')
+        self.assertEqual(w.ex(Number(13, 'mV')), '13.0')
+
+    def test_name(self):
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        self.assertRaisesRegex(
+            NotImplementedError, 'Partial',
+            self.w.ex, myokit.PartialDerivative(self.a, self.b))
+
+    def test_initial_value(self):
+        self.assertRaisesRegex(
+            NotImplementedError, 'Initial',
+            self.w.ex, myokit.InitialValue(self.a))
+
+    def test_prefix_plus_minus(self):
+        # Inherited from c-based
+
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0f')
+        self.eq(PrefixPlus(PrefixPlus(p)), '+(+11.0f)')
+        self.eq(PrefixPlus(PrefixPlus(PrefixPlus(p))), '+(+(+11.0f))')
+        self.eq(PrefixPlus(Number('+1')), '+1.0f')
+        self.eq(PrefixMinus(p), '-11.0f')
+        self.eq(PrefixMinus(PrefixMinus(p)), '-(-11.0f)')
+        self.eq(PrefixMinus(Number(-1)), '-(-1.0f)')
+        self.eq(PrefixMinus(PrefixMinus(Number(-2))), '-(-(-2.0f))')
+
+        # Test with operators of precedence SUM, PRODUCT
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), '+(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(PrefixPlus(Divide(b, a)), '+(b / a)')
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(PrefixMinus(Divide(b, a)), '-(b / a)')
+
+    def test_prefix_plus_minus_double(self):
+        # Inherited from c-based
+
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        p = Number(3, 'mA')
+        self.assertEqual(w.ex(PrefixPlus(p)), '+3.0')
+        self.assertEqual(w.ex(PrefixPlus(PrefixPlus(p))), '+(+3.0)')
+        self.assertEqual(
+            w.ex(PrefixPlus(PrefixPlus(PrefixPlus(p)))), '+(+(+3.0))')
+        self.assertEqual(w.ex(PrefixPlus(Number('+1'))), '+1.0')
+        self.assertEqual(w.ex(PrefixMinus(p)), '-3.0')
+        self.assertEqual(w.ex(PrefixMinus(PrefixMinus(p))), '-(-3.0)')
+        self.assertEqual(w.ex(PrefixMinus(Number(-1))), '-(-1.0)')
+        self.assertEqual(
+            w.ex(PrefixMinus(PrefixMinus(Number(-2)))), '-(-(-2.0))')
+
+    def test_arithmetic(self):
+        # Inherited from c-based
+
+        a, b, c = self.abc
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Multiply(a, Plus(b, c)), 'a * (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Divide(a, Minus(b, c)), 'a / (b - c)')
+        self.eq(Plus(Divide(a, b), c), 'a / b + c')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+
+    def test_quotient_remainder(self):
+        # Inherited from c-based
+
+        a, b, c = self.abc
+        self.eq(Quotient(a, Divide(b, c)), 'floorf(a / (b / c))')
+        self.eq(Remainder(Plus(a, c), b), '(a + c - b * floorf((a + c) / b))')
+        self.eq(Divide(c, Remainder(b, a)), 'c / ((b - a * floorf(b / a)))')
+
+    def test_quotient_remainder_double(self):
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        a, b, c = self.abc
+        self.assertEqual(w.ex(Quotient(a, b)), 'floor(comp.a / comp.b)')
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'powf(a, b)')
+        self.eq(Power(Power(a, b), c), 'powf(powf(a, b), c)')
+        self.eq(Power(a, Power(b, c)), 'powf(a, powf(b, c))')
+
+        self.eq(Power(Plus(a, b), c), 'powf(a + b, c)')
+        self.eq(Power(a, Minus(b, c)), 'powf(a, b - c)')
+        self.eq(Power(Multiply(a, b), c), 'powf(a * b, c)')
+        self.eq(Power(a, Divide(b, c)), 'powf(a, b / c)')
+
+    def test_power_double(self):
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        a, b, c = self.abc
+        self.assertEqual(w.ex(Power(a, b)), 'pow(comp.a, comp.b)')
+        self.assertEqual(w.ex(Power(Plus(a, b), c)),
+                         'pow(comp.a + comp.b, comp.c)')
+
+    def test_log(self):
+        a, b = self.ab
+        self.eq(Log(a), 'logf(a)')
+        self.eq(Log10(a), 'log10f(a)')
+        self.eq(Log(a, b), '(logf(a) / logf(b))')
+
+    def test_log_double(self):
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        a, b = self.ab
+        self.assertEqual(w.ex(Log(a)), 'log(comp.a)')
+        self.assertEqual(w.ex(Log10(b)), 'log10(comp.b)')
+        self.assertEqual(w.ex(Log(b, a)), '(log(comp.b) / log(comp.a))')
+
+    def test_functions(self):
+        a = self.a
+        self.eq(Sqrt(self.a), 'sqrtf(a)')
+        self.eq(Exp(self.a), 'expf(a)')
+        self.eq(Sin(self.a), 'sinf(a)')
+        self.eq(Cos(self.a), 'cosf(a)')
+        self.eq(Tan(self.a), 'tanf(a)')
+        self.eq(ASin(self.a), 'asinf(a)')
+        self.eq(ACos(self.a), 'acosf(a)')
+        self.eq(ATan(self.a), 'atanf(a)')
+        self.eq(Floor(self.a), 'floorf(a)')
+        self.eq(Ceil(self.a), 'ceilf(a)')
+        self.eq(Abs(self.a), 'fabsf(a)')
+
+    def test_functions_double(self):
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        self.assertEqual(w.ex(Sqrt(self.a)), 'sqrt(comp.a)')
+        self.assertEqual(w.ex(Exp(self.a)), 'exp(comp.a)')
+        self.assertEqual(w.ex(Sin(self.a)), 'sin(comp.a)')
+        self.assertEqual(w.ex(Cos(self.a)), 'cos(comp.a)')
+        self.assertEqual(w.ex(Tan(self.a)), 'tan(comp.a)')
+        self.assertEqual(w.ex(ASin(self.a)), 'asin(comp.a)')
+        self.assertEqual(w.ex(ACos(self.a)), 'acos(comp.a)')
+        self.assertEqual(w.ex(ATan(self.a)), 'atan(comp.a)')
+        self.assertEqual(w.ex(Floor(self.a)), 'floor(comp.a)')
+        self.assertEqual(w.ex(Ceil(self.a)), 'ceil(comp.a)')
+        self.assertEqual(w.ex(Abs(self.a)), 'fabs(comp.a)')
+
+    def test_conditions(self):
+        # Inherited from c-based
+
+        a, b, c, d = self.abcd
+        self.eq(And(a, b), '(a && b)')
+        self.eq(Or(d, c), '(d || c)')
+        self.eq(Not(And(a, b)), '(!(a && b))')
+        self.eq(Not(c), '(!(c))')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)), '((d > c) || (b < a))')
+        self.eq(Not(Or(Number(1), Number(2))), '(!(1.0f || 2.0f))')
+        self.eq(Not(Less(Number(1), Number(2))), '(!(1.0f < 2.0f))')
+        self.eq(Not(Plus(Number(1), Number(2))), '(!(1.0f + 2.0f))')
+
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0f == 0.0f) == 0.0f)')
+
+    def test_conditionals(self):
+        # Inherited from c-based
+
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), '((a == b) ? d : c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a), '((d != c) ? b : a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                '((a == b) ? c : ((a == d) ? 3.0f : 4.0f))')
+
+        # If condition is not a condition
+        self.eq(If(a, d, c), '((a) ? d : c)')
+        self.eq(Piecewise(a, b, c, d, Number(4)),
+                '((a) ? b : ((c) ? d : 4.0f))')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_easyml.py b/myokit/tests/test_formats_easyml.py
index 8994e0901..58c29a7d6 100755
--- a/myokit/tests/test_formats_easyml.py
+++ b/myokit/tests/test_formats_easyml.py
@@ -11,6 +11,15 @@
 import myokit
 import myokit.formats
 import myokit.formats.easyml
+import myokit.tests
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
 
 from myokit.tests import TemporaryDirectory, WarningCollector, DIR_DATA
 
@@ -247,164 +256,172 @@ def test_capability_reporting(self):
         self.assertTrue(e.supports_model())
 
 
-class EasyMLExpressionWriterTest(unittest.TestCase):
-    """ Tests EasyML expression writer functionality. """
-
-    def test_all(self):
-        w = myokit.formats.ewriter('easyml')
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-        # Integer
-        c = myokit.Number(1)
-        self.assertEqual(w.ex(c), '1.0')
-        # Integer
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # 1 - exp() and exp() - 1
-        x = myokit.Minus(myokit.Exp(myokit.Number(2)), myokit.Number(1))
-        self.assertEqual(w.ex(x), 'expm1(2.0)')
-        x = myokit.Minus(myokit.Number(1), myokit.Exp(myokit.Number(3)))
-        self.assertEqual(w.ex(x), '-expm1(3.0)')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
+class EasyMLExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """ Test conversion to EasyML syntax. """
+    _name = 'easyml'
+    _target = myokit.formats.easyml.EasyMLExpressionWriter
+
+    def test_number(self):
+        self.eq(Number(1), '1.0')
+        self.eq(Number(-2), '-2.0')
+        self.eq(Number(13, 'mV'), '13.0')
+
+    def test_name(self):
+        # Inherited from CBasedExpressionWriter
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        # Inherited from CBasedExpressionWriter
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        e = myokit.PartialDerivative(self.a, self.b)
+        self.assertRaisesRegex(NotImplementedError, 'Partial', self.w.ex, e)
+
+    def test_initial_value(self):
+        e = myokit.InitialValue(self.a)
+        self.assertRaisesRegex(NotImplementedError, 'Initial', self.w.ex, e)
+
+    def test_prefix_plus_minus(self):
+        # Inherited from CBasedExpressionWriter
+        p = Number(11, 'kV')
+        a, b, c = self.abc
+        self.eq(PrefixPlus(p), '+11.0')
+        self.eq(PrefixPlus(PrefixPlus(PrefixPlus(p))), '+(+(+11.0))')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(PrefixMinus(p), '-11.0')
+        self.eq(PrefixMinus(PrefixMinus(p)), '-(-11.0)')
+        self.eq(PrefixMinus(Number(-1)), '-(-1.0)')
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(PrefixMinus(Divide(b, a)), '-(b / a)')
+
+    def test_plus_minus(self):
+        a, b, c = self.abc
+        self.eq(Plus(a, b), 'a + b')
+        self.eq(Plus(Plus(a, b), c), 'a + b + c')
+        self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Minus(Minus(a, b), c), 'a - b - c')
+        self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Plus(Minus(a, b), c), 'a - b + c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+
+        # Substitution of expm for 1 - exp() and exp() - 1
+        self.eq(Minus(Exp(Number(2)), Number(1)), 'expm1(2.0)')
+        self.eq(Minus(Number(1), Exp(Number(3))), '-expm1(3.0)')
+
+    def test_multiply_divide(self):
+        # Inherited from CBasedExpressionWriter
+        a, b, c = self.abc
+        self.eq(Multiply(a, b), 'a * b')
+        self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
+        self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
+        self.eq(Divide(a, b), 'a / b')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+
+    def test_quotient(self):
+        # Inherited from CBasedExpressionWriter
+        a, b, c = self.abc
+        with WarningCollector():
+            self.eq(Quotient(a, b), 'floor(a / b)')
+            self.eq(Quotient(Plus(a, c), b), 'floor((a + c) / b)')
+            self.eq(Quotient(Divide(a, c), b), 'floor(a / c / b)')
+            self.eq(Quotient(a, Divide(b, c)), 'floor(a / (b / c))')
+            self.eq(Multiply(Quotient(a, b), c), 'floor(a / b) * c')
+            self.eq(Multiply(c, Quotient(a, b)), 'c * (floor(a / b))')
+
+    def test_remainder(self):
+        # Inherited from CBasedExpressionWriter
+        a, b, c = self.abc
+        with WarningCollector():
+            self.eq(Remainder(a, b), '(a - b * floor(a / b))')
+            self.eq(Remainder(Plus(a, c), b),
+                    '(a + c - b * floor((a + c) / b))')
+            self.eq(Multiply(Remainder(a, b), c), '(a - b * floor(a / b)) * c')
+            self.eq(Divide(c, Remainder(b, a)), 'c / ((b - a * floor(b / a)))')
+
+    def test_power(self):
+        # Inherited from CBasedExpressionWriter
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'pow(a, b)')
+        self.eq(Power(Power(a, b), c), 'pow(pow(a, b), c)')
+        self.eq(Power(a, Power(b, c)), 'pow(a, pow(b, c))')
+
+    def test_log(self):
+        # Inherited from CBasedExpressionWriter
+        a, b = self.ab
+        self.eq(Log(a), 'log(a)')
+        self.eq(Log10(a), 'log10(a)')
+        self.eq(Log(a, b), '(log(a) / log(b))')
+
+    def test_functions(self):
+        a, b = self.ab
+
+        self.eq(Sqrt(a), 'sqrt(a)')
+        self.eq(Exp(a), 'exp(a)')
+        self.eq(Cos(a), 'cos(a)')
+        self.eq(ACos(a), 'acos(a)')
+        self.eq(Abs(a), 'fabs(a)')
+
         with WarningCollector() as c:
-            self.assertEqual(w.ex(x), 'floor(c.a / 12.0)')
-        # Remainder
-        x = myokit.Remainder(a, b)
+            self.eq(Sin(a), 'sin(a)')
+        self.assertIn('Unsupported', c.text())
+
         with WarningCollector() as c:
-            self.assertEqual(w.ex(x), 'c.a - 12.0 * (floor(c.a / 12.0))')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'pow(c.a, 12.0)')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), '(log(c.a) / log(12.0))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'log10(12.0)')
-
-        # Sin
+            self.eq(Tan(a), 'tan(a)')
+        self.assertIn('Unsupported', c.text())
+
         with WarningCollector() as c:
-            x = myokit.Sin(b)
-            self.assertEqual(w.ex(x), 'sin(12.0)')
-            # Cos
-            x = myokit.Cos(b)
-            self.assertEqual(w.ex(x), 'cos(12.0)')
-            # Tan
-            x = myokit.Tan(b)
-            self.assertEqual(w.ex(x), 'tan(12.0)')
-            # ASin
-            x = myokit.ASin(b)
-            self.assertEqual(w.ex(x), 'asin(12.0)')
-            # ACos
-            x = myokit.ACos(b)
-            self.assertEqual(w.ex(x), 'acos(12.0)')
-            # ATan
-            x = myokit.ATan(b)
-            self.assertEqual(w.ex(x), 'atan(12.0)')
+            self.eq(ASin(a), 'asin(a)')
+        self.assertIn('Unsupported', c.text())
 
         with WarningCollector() as c:
-            # Floor
-            x = myokit.Floor(b)
-            self.assertEqual(w.ex(x), 'floor(12.0)')
-            # Ceil
-            x = myokit.Ceil(b)
-            self.assertEqual(w.ex(x), 'ceil(12.0)')
-            # Abs
-            x = myokit.Abs(b)
-            self.assertEqual(w.ex(x), 'fabs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), '!((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) and (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) or (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) ? c.a : 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            '((5.0 > 3.0) ? c.a : ((2.0 < 1.0) ? 12.0 : 1.0))')
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-    def test_easyml_ewriter_fetching(self):
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('easyml')
-        self.assertIsInstance(w, myokit.formats.easyml.EasyMLExpressionWriter)
+            self.eq(ATan(a), 'atan(a)')
+        self.assertIn('Unsupported', c.text())
+
+        with WarningCollector() as c:
+            self.eq(Floor(a), 'floor(a)')
+        self.assertIn('Unsupported', c.text())
+
+        with WarningCollector() as c:
+            self.eq(Ceil(a), 'ceil(a)')
+        self.assertIn('Unsupported', c.text())
+
+    def test_conditions(self):
+        # Inherited from CBasedExpressionWriter
+        a, b, c, d = self.abcd
+        self.eq(Not(And(a, b)), '(!(a && b))')
+        self.eq(Not(c), '(!(c))')
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
+        self.eq(Or(More(d, c), MoreEqual(b, a)), '((d > c) || (b >= a))')
+        self.eq(Or(Less(d, c), LessEqual(b, a)), '((d < c) || (b <= a))')
+        self.eq(Not(Or(Number(1), Number(2))), '(!(1.0 || 2.0))')
+        self.eq(Not(Less(Number(1), Number(2))), '(!(1.0 < 2.0))')
+        self.eq(Not(Plus(Number(1), Number(2))), '(!(1.0 + 2.0))')
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0 == 0.0) == 0.0)')
+
+    def test_conditionals(self):
+        # Inherited from CBasedExpressionWriter
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), '((a == b) ? d : c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a), '((d != c) ? b : a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                '((a == b) ? c : ((a == d) ? 3.0 : 4.0))')
+        self.eq(If(a, d, c), '((a) ? d : c)')
+        self.eq(Piecewise(a, b, c, d, Number(4)),
+                '((a) ? b : ((c) ? d : 4.0))')
 
 
 if __name__ == '__main__':
     unittest.main()
-
diff --git a/myokit/tests/test_formats_exporters.py b/myokit/tests/test_formats_exporters_run.py
similarity index 71%
rename from myokit/tests/test_formats_exporters.py
rename to myokit/tests/test_formats_exporters_run.py
index ade94d839..eccc42365 100755
--- a/myokit/tests/test_formats_exporters.py
+++ b/myokit/tests/test_formats_exporters_run.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-# Tests the exporters from the format module.
+# Tests that exporters run: doesn't check output
 #
 # This file is part of Myokit.
 # See http://myokit.org for copyright, sharing, and licensing details.
@@ -95,74 +95,6 @@ def _test(self, e, model=None, protocol=None):
                 raise Exception(
                     'No types of export supported by: ' + exporter)
 
-    def test_runnable_exporter_shared(self):
-        # Test shared functionality of the TemplatedRunnableExporters.
-
-        e = myokit.formats.exporter('ansic')
-
-        # Load model, protocol
-        m, p, x = myokit.load('example')
-
-        # Create empty output directory as subdirectory of DIR_OUT
-        with TemporaryDirectory() as d:
-            path = d.path()
-
-            # Simple export
-            dpath = os.path.join(path, 'runnable1')
-            ret = e.runnable(dpath, m)
-            self.assertIsNone(ret)
-            self.assertTrue(os.path.isdir(dpath))
-            self.assertTrue(len(os.listdir(dpath)) > 0)
-
-            # Write to complex path
-            dpath = os.path.join(path, 'runnable2', 'nest', 'test')
-            ret = e.runnable(dpath, m, p)
-            self.assertIsNone(ret)
-            self.assertTrue(os.path.isdir(dpath))
-            self.assertTrue(len(os.listdir(dpath)) > 0)
-
-            # Overwrite existing path
-            ret = e.runnable(dpath, m, p)
-            self.assertIsNone(ret)
-            self.assertTrue(os.path.isdir(dpath))
-            self.assertTrue(len(os.listdir(dpath)) > 0)
-
-            # Path pointing to file
-            dpath = os.path.join(path, 'file')
-            with open(dpath, 'w') as f:
-                f.write('contents\n')
-            self.assertRaisesRegex(
-                myokit.ExportError, 'file exists', e.runnable, dpath, m, p)
-
-            # Directory exists where we're trying to write a file
-            dpath = os.path.join(path, 'runnable3')
-            fname = os.path.join(dpath, 'sim.c')
-            os.makedirs(fname)
-            self.assertRaisesRegex(
-                myokit.ExportError, 'Directory exists',
-                e.runnable, dpath, m, p)
-
-            # Directory embedded in the output file path
-            def embedded():
-                return {'sim.c': 'nested/sim.c'}
-
-            # 1. Normal operation
-            e._dict = embedded
-            dpath = os.path.join(path, 'runnable4')
-            ret = e.runnable(dpath, m, p)
-            self.assertIsNone(ret)
-            self.assertTrue(os.path.isdir(dpath))
-            self.assertTrue(len(os.listdir(dpath)) > 0)
-
-            # 2. Try to create directory where file exists
-            def embedded():
-                return {'sim.c': 'nested/sim.c/som.c'}
-
-            e._dict = embedded
-            dpath = os.path.join(path, 'runnable4')
-            self.assertRaisesRegex(
-                myokit.ExportError, 'file or link', e.runnable, dpath, m, p)
-
     def test_completeness(self):
         # Test that all exporters have a test (so meta!).
 
diff --git a/myokit/tests/test_formats_expression_writers.py b/myokit/tests/test_formats_expression_writers.py
deleted file mode 100755
index fca45f9d7..000000000
--- a/myokit/tests/test_formats_expression_writers.py
+++ /dev/null
@@ -1,1281 +0,0 @@
-#!/usr/bin/env python3
-#
-# Tests the expression writer classes.
-#
-# This file is part of Myokit.
-# See http://myokit.org for copyright, sharing, and licensing details.
-#
-import unittest
-
-import myokit
-import myokit.formats
-import myokit.formats.ansic
-import myokit.formats.cpp
-import myokit.formats.latex
-import myokit.formats.matlab
-import myokit.formats.python
-import myokit.formats.stan
-
-
-class ExpressionWriterTest(unittest.TestCase):
-    """ Test shared expression writer functionality. """
-
-    def test_ewriter_interface(self):
-        # Test listing and creating expression writers.
-
-        # Test listing
-        es = myokit.formats.ewriters()
-        self.assertTrue(len(es) > 0)
-
-        # Create one of each
-        for e in es:
-            self.assertIsInstance(e, str)
-            e = myokit.formats.ewriter(e)
-            self.assertTrue(isinstance(e, myokit.formats.ExpressionWriter))
-
-    def test_unknown(self):
-        # Test requesting an unknown expression writer.
-        # Test fetching using ewriter method
-        self.assertRaisesRegex(
-            KeyError, 'Expression writer not found', myokit.formats.ewriter,
-            'dada')
-
-
-class AnsicExpressionWriterTest(unittest.TestCase):
-    """ Test the Ansi C ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.ansic.AnsiCExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(w.ex(x), 'floor(c.a / 12.0)')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0 * (floor(c.a / 12.0))')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'pow(c.a, 12.0)')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), '(log(c.a) / log(12.0))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), 'sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), 'cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), 'tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), 'asin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), 'acos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), 'atan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), 'floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), 'ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), 'fabs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), '!((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) && (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) || (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) ? c.a : 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            '((5.0 > 3.0) ? c.a : ((2.0 < 1.0) ? 12.0 : 1.0))')
-
-        # If/Piecewise with special function
-        w.set_condition_function('ifthenelse')
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), 'ifthenelse((5.0 > 3.0), c.a, 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            'ifthenelse((5.0 > 3.0), c.a, ifthenelse((2.0 < 1.0), 12.0, 1.0))')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('ansic')
-        self.assertIsInstance(w, myokit.formats.ansic.AnsiCExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class CppExpressionWriterTest(unittest.TestCase):
-    """ Test the C++ ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.cpp.CppExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(w.ex(x), 'floor(c.a / 12.0)')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0 * (floor(c.a / 12.0))')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'pow(c.a, 12.0)')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), '(log(c.a) / log(12.0))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), 'sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), 'cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), 'tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), 'asin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), 'acos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), 'atan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), 'floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), 'ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), 'fabs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), '!((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) && (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) || (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) ? c.a : 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            '((5.0 > 3.0) ? c.a : ((2.0 < 1.0) ? 12.0 : 1.0))')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('cpp')
-        self.assertIsInstance(w, myokit.formats.cpp.CppExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class CudaExpressionWriterTest(unittest.TestCase):
-    """ Test the CUDA ewriter class. """
-
-    def test_all(self):
-        # Single and double precision
-        ws = myokit.formats.cuda.CudaExpressionWriter()
-        wd = myokit.formats.cuda.CudaExpressionWriter(myokit.DOUBLE_PRECISION)
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(ws.ex(a), 'c.a')
-        self.assertEqual(wd.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(ws.ex(b), '12.0f')
-        self.assertEqual(wd.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(ws.ex(x), '12.0f')
-        self.assertEqual(wd.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(ws.ex(x), '(-12.0f)')
-        self.assertEqual(wd.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(ws.ex(x), 'c.a + 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(ws.ex(x), 'c.a - 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(ws.ex(x), 'c.a * 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(ws.ex(x), 'c.a / 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a / 12.0')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(ws.ex(x), 'floorf(c.a / 12.0f)')
-        self.assertEqual(wd.ex(x), 'floor(c.a / 12.0)')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(ws.ex(x), 'c.a - 12.0f * (floorf(c.a / 12.0f))')
-        self.assertEqual(wd.ex(x), 'c.a - 12.0 * (floor(c.a / 12.0))')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(ws.ex(x), 'powf(c.a, 12.0f)')
-        self.assertEqual(wd.ex(x), 'pow(c.a, 12.0)')
-        # Square
-        x = myokit.Power(a, myokit.Number(2))
-        self.assertEqual(ws.ex(x), '(c.a * c.a)')
-        self.assertEqual(wd.ex(x), '(c.a * c.a)')
-        # Square with brackets
-        x = myokit.Power(myokit.Plus(a, b), myokit.Number(2))
-        self.assertEqual(ws.ex(x), '((c.a + 12.0f) * (c.a + 12.0f))')
-        self.assertEqual(wd.ex(x), '((c.a + 12.0) * (c.a + 12.0))')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(ws.ex(x), 'sqrtf(12.0f)')
-        self.assertEqual(wd.ex(x), 'sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(ws.ex(x), 'expf(c.a)')
-        self.assertEqual(wd.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(ws.ex(x), 'logf(12.0f)')
-        self.assertEqual(wd.ex(x), 'log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(ws.ex(x), '(logf(c.a) / logf(12.0f))')
-        self.assertEqual(wd.ex(x), '(log(c.a) / log(12.0))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(ws.ex(x), 'log10f(12.0f)')
-        self.assertEqual(wd.ex(x), 'log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(ws.ex(x), 'sinf(12.0f)')
-        self.assertEqual(wd.ex(x), 'sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(ws.ex(x), 'cosf(12.0f)')
-        self.assertEqual(wd.ex(x), 'cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(ws.ex(x), 'tanf(12.0f)')
-        self.assertEqual(wd.ex(x), 'tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(ws.ex(x), 'asinf(12.0f)')
-        self.assertEqual(wd.ex(x), 'asin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(ws.ex(x), 'acosf(12.0f)')
-        self.assertEqual(wd.ex(x), 'acos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(ws.ex(x), 'atanf(12.0f)')
-        self.assertEqual(wd.ex(x), 'atan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(ws.ex(x), 'floorf(12.0f)')
-        self.assertEqual(wd.ex(x), 'floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(ws.ex(x), 'ceilf(12.0f)')
-        self.assertEqual(wd.ex(x), 'ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(ws.ex(x), 'fabsf(12.0f)')
-        self.assertEqual(wd.ex(x), 'fabs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(ws.ex(x), '(c.a == 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(ws.ex(x), '(c.a != 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(ws.ex(x), '(c.a > 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(ws.ex(x), '(c.a < 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(ws.ex(x), '(c.a >= 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(ws.ex(x), '(c.a <= 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(ws.ex(x), '!((5.0f > 3.0f))')
-        self.assertEqual(wd.ex(x), '!((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) && (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) && (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) || (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) || (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) ? c.a : 12.0f)')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) ? c.a : 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            ws.ex(x),
-            '((5.0f > 3.0f) ? c.a : ((2.0f < 1.0f) ? 12.0f : 1.0f))')
-        self.assertEqual(
-            wd.ex(x),
-            '((5.0 > 3.0) ? c.a : ((2.0 < 1.0) ? 12.0 : 1.0))')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('cuda')
-        self.assertIsInstance(w, myokit.formats.cuda.CudaExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class LatexExpressionWriterTest(unittest.TestCase):
-    """ Test the Latex ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.latex.LatexExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Model needs to be validated --> sets unames
-        avar.set_rhs(12)
-        avar.set_binding('time')
-        model.validate()
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), '\\text{a}')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '\\left(-12.0\\right)')
-        # Prefix minus with bracket
-        x = myokit.PrefixMinus(myokit.Plus(a, b))
-        self.assertEqual(
-            w.ex(x), '\\left(-\\left(\\text{a}+12.0\\right)\\right)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), '\\text{a}+12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), '\\text{a}-12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), '\\text{a}*12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), '\\frac{\\text{a}}{12.0}')
-
-        # Quotient
-        # Not supported in latex!
-        x = myokit.Quotient(a, b)
-        self.assertEqual(
-            w.ex(x), '\\left\\lfloor\\frac{\\text{a}}{12.0}\\right\\rfloor')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), '\\bmod\\left(\\text{a},12.0\\right)')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), '\\text{a}^{12.0}')
-        # Power with brackets
-        x = myokit.Power(myokit.Plus(a, b), b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}+12.0\\right)^{12.0}')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), '\\sqrt{12.0}')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), '\\exp\\left(\\text{a}\\right)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), '\\log\\left(12.0\\right)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), '\\log_{12.0}\\left(\\text{a}\\right)')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), '\\log_{10.0}\\left(12.0\\right)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), '\\sin\\left(12.0\\right)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), '\\cos\\left(12.0\\right)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), '\\tan\\left(12.0\\right)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), '\\arcsin\\left(12.0\\right)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), '\\arccos\\left(12.0\\right)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), '\\arctan\\left(12.0\\right)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), '\\left\\lfloor{12.0}\\right\\rfloor')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), '\\left\\lceil{12.0}\\right\\rceil')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), '\\lvert{12.0}\\rvert')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}=12.0\\right)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}\\neq12.0\\right)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}>12.0\\right)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}<12.0\\right)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}\\geq12.0\\right)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '\\left(\\text{a}\\leq12.0\\right)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(
-            w.ex(x), '\\not\\left(\\left(5.0>3.0\\right)\\right)')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(
-            w.ex(x),
-            '\\left(\\left(5.0>3.0\\right)\\and'
-            '\\left(2.0<1.0\\right)\\right)')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(
-            w.ex(x),
-            '\\left(\\left(5.0>3.0\\right)\\or'
-            '\\left(2.0<1.0\\right)\\right)')
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(
-            w.ex(x), 'if\\left(\\left(5.0>3.0\\right),\\text{a},12.0\\right)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            'piecewise\\left(\\left(5.0>3.0\\right),\\text{a},'
-            '\\left(2.0<1.0\\right),12.0,1.0\\right)')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('latex')
-        self.assertIsInstance(w, myokit.formats.latex.LatexExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class MatlabExpressionWriterTest(unittest.TestCase):
-    """ Test the Matlab ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.matlab.MatlabExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # Quotient
-
-        x = myokit.Quotient(a, b)
-        self.assertEqual(w.ex(x), 'floor(c.a / 12.0)')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), 'mod(c.a, 12.0)')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'c.a ^ 12.0')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), '(log(c.a) / log(12.0))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), 'sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), 'cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), 'tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), 'asin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), 'acos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), 'atan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), 'floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), 'ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), 'abs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), '!((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) && (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) || (2.0 < 1.0))')
-
-        # If (custom function)
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), 'ifthenelse((5.0 > 3.0), c.a, 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            'ifthenelse((5.0 > 3.0), c.a, ifthenelse((2.0 < 1.0), 12.0, 1.0))')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('matlab')
-        self.assertIsInstance(w, myokit.formats.matlab.MatlabExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class NumPyExpressionWriterTest(unittest.TestCase):
-    """ Test the NumPy ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.python.NumPyExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(w.ex(x), 'c.a // 12.0')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), 'c.a % 12.0')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'c.a ** 12.0')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'numpy.sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'numpy.exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'numpy.log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), 'numpy.log(c.a, 12.0)')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'numpy.log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), 'numpy.sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), 'numpy.cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), 'numpy.tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), 'numpy.arcsin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), 'numpy.arccos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), 'numpy.arctan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), 'numpy.floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), 'numpy.ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), 'abs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), 'not ((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) and (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) or (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(
-            w.ex(x), 'numpy.select([(5.0 > 3.0)], [c.a], 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            'numpy.select([(5.0 > 3.0), (2.0 < 1.0)], [c.a, 12.0], 1.0)')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('numpy')
-        self.assertIsInstance(w, myokit.formats.python.NumPyExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class PythonExpressionWriterTest(unittest.TestCase):
-    """ Test the Python ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.python.PythonExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Derivative
-        x = myokit.Derivative(a)
-        self.assertEqual(w.ex(x), 'dot(c.a)')
-        # Partial derivative
-        x = myokit.PartialDerivative(a, a)
-        self.assertEqual(w.ex(x), 'diff(c.a, c.a)')
-        # Initial value
-        x = myokit.InitialValue(a)
-        self.assertEqual(w.ex(x), 'init(c.a)')
-
-        # Number
-        b = myokit.Number(3)
-        self.assertEqual(w.ex(b), '3.0')
-        # Number with unit
-        b = myokit.Number(12, 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(w.ex(x), 'c.a // 12.0')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), 'c.a % 12.0')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'c.a ** 12.0')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'math.sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'math.exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'math.log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), 'math.log(c.a, 12.0)')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'math.log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), 'math.sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), 'math.cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), 'math.tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), 'math.asin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), 'math.acos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), 'math.atan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), 'math.floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), 'math.ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), 'abs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), 'not ((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) and (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) or (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), '(c.a if (5.0 > 3.0) else 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            '(c.a if (5.0 > 3.0) else (12.0 if (2.0 < 1.0) else 1.0))')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('python')
-        self.assertIsInstance(w, myokit.formats.python.PythonExpressionWriter)
-
-        # Test lhs method
-        w.set_lhs_function(lambda x: 'sheep')
-        self.assertEqual(w.ex(a), 'sheep')
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-class StanExpressionWriterTest(unittest.TestCase):
-    """ Test the Stan ewriter class. """
-
-    def test_all(self):
-        w = myokit.formats.stan.StanExpressionWriter()
-
-        model = myokit.Model()
-        component = model.add_component('c')
-        avar = component.add_variable('a')
-
-        # Name
-        a = myokit.Name(avar)
-        self.assertEqual(w.ex(a), 'c.a')
-        # Number with unit
-        b = myokit.Number('12', 'pF')
-        self.assertEqual(w.ex(b), '12.0')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(w.ex(x), '12.0')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(w.ex(x), '(-12.0)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(w.ex(x), 'c.a + 12.0')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(w.ex(x), 'c.a - 12.0')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(w.ex(x), 'c.a * 12.0')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(w.ex(x), 'c.a / 12.0')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(w.ex(x), 'floor(c.a / 12.0)')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(w.ex(x), 'fmod(c.a, 12.0)')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), 'c.a ^ 12.0')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(w.ex(x), 'sqrt(12.0)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(w.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(w.ex(x), 'log(12.0)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(w.ex(x), '(log(c.a) / log(12.0))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(w.ex(x), 'log10(12.0)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(w.ex(x), 'sin(12.0)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(w.ex(x), 'cos(12.0)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(w.ex(x), 'tan(12.0)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(w.ex(x), 'asin(12.0)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(w.ex(x), 'acos(12.0)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(w.ex(x), 'atan(12.0)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(w.ex(x), 'floor(12.0)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(w.ex(x), 'ceil(12.0)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(w.ex(x), 'abs(12.0)')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(w.ex(x), '(c.a == 12.0)')
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a != 12.0)')
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(w.ex(x), '(c.a > 12.0)')
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(w.ex(x), '(c.a < 12.0)')
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a >= 12.0)')
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(w.ex(x), '(c.a <= 12.0)')
-
-        # Not
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        x = myokit.Not(cond1)
-        self.assertEqual(w.ex(x), '!((5.0 > 3.0))')
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) && (2.0 < 1.0))')
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) || (2.0 < 1.0))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(w.ex(x), '((5.0 > 3.0) ? c.a : 12.0)')
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            w.ex(x),
-            '((5.0 > 3.0) ? c.a : ((2.0 < 1.0) ? 12.0 : 1.0))')
-
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('stan')
-        self.assertIsInstance(w, myokit.formats.stan.StanExpressionWriter)
-
-        # Test without a Myokit expression
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/myokit/tests/test_formats_html.py b/myokit/tests/test_formats_html.py
index 08d0f38c7..6939bd2a2 100755
--- a/myokit/tests/test_formats_html.py
+++ b/myokit/tests/test_formats_html.py
@@ -112,9 +112,7 @@
 
 
 class AsciifierTest(unittest.TestCase):
-    """
-    Tests the method to convert html to ascii.
-    """
+    """ Tests converting HTML to ASCII. """
     def test_asciify(self):
 
         # Compare line by line
diff --git a/myokit/tests/test_formats_importers.py b/myokit/tests/test_formats_importers.py
deleted file mode 100755
index 6e0d84603..000000000
--- a/myokit/tests/test_formats_importers.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-#
-# Tests the importers for various formats
-#
-# This file is part of Myokit.
-# See http://myokit.org for copyright, sharing, and licensing details.
-#
-import os
-import unittest
-
-import myokit
-import myokit.formats as formats
-
-from myokit.tests import DIR_FORMATS
-
-
-class ImporterTest(unittest.TestCase):
-    """ Test shared importer functionality. """
-
-    def test_importer_interface(self):
-        # Test listing and creating importers.
-        ims = myokit.formats.importers()
-        self.assertTrue(len(ims) > 0)
-        for i in ims:
-            self.assertIsInstance(i, str)
-            i = myokit.formats.importer(i)
-            self.assertTrue(isinstance(i, myokit.formats.Importer))
-
-    def test_unknown(self):
-        # Test requesting an unknown importer.
-        # Test fetching using importer method
-        self.assertRaisesRegex(
-            KeyError, 'Importer not found', myokit.formats.importer, 'blip')
-
-
-class AxonTest(unittest.TestCase):
-    """ Partially tests Axon formats importing. """
-
-    def test_capability_reporting(self):
-        # Test if the right capabilities are reported.
-        i = formats.importer('abf')
-        self.assertFalse(i.supports_component())
-        self.assertFalse(i.supports_model())
-        self.assertTrue(i.supports_protocol())
-
-    def test_protocol(self):
-        i = formats.importer('abf')
-        self.assertTrue(i.supports_protocol())
-        i.protocol(os.path.join(DIR_FORMATS, 'abf-v1.abf'))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/myokit/tests/test_formats_latex.py b/myokit/tests/test_formats_latex.py
new file mode 100755
index 000000000..14a8b7ef9
--- /dev/null
+++ b/myokit/tests/test_formats_latex.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+#
+# Tests writing of Stan equations.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import unittest
+
+import myokit
+import myokit.formats.latex
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+)
+
+import myokit.tests
+
+
+class LatexExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test conversion of expressions to Python.
+    This is used by pyfunc(), although that's usually done with the
+    NumPyExpressionWriter instead.
+    Numerical tests are provided.
+    """
+    _name = 'latex'
+    _target = myokit.formats.latex.LatexExpressionWriter
+    _update_lhs_function = False
+
+    def test_number(self):
+        self.eq(Number(1), '1.0')
+        self.eq(Number(-1.3274924373284374), '-1.32749243732843736e+00')
+        self.eq(Number(+1.3274924373284374), '1.32749243732843736e+00')
+        self.eq(Number(-2), '-2.0')
+        self.eq(Number(13, 'mV'), r'13.0 \text{mV}')
+        self.eq(Number(2, 'A/F'), r'2.0 \text{A/F}')
+        self.eq(Number(7, myokit.units.dimensionless), '7.0')
+
+    def test_name(self):
+        self.eq(self.a, r'\text{a}')
+        m = myokit.Model()
+        c = m.add_component('c')
+        v = c.add_variable('under_score', rhs=3)
+        t = c.add_variable('time', rhs=0, binding='time')
+        m.validate()
+        self.eq(myokit.Name(v), r'\text{under\_score}')
+
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), r'\frac{d\text{a}}{d\text{t}}')
+
+        w = self._target()
+        w.set_time_variable_name('timmy')
+        self.assertEqual(w.ex(myokit.Derivative(self.a)),
+                         r'\frac{d\text{a}}{d\text{timmy}}')
+        w.set_time_variable_name('Bob')
+        self.assertEqual(w.ex(myokit.Derivative(self.a)),
+                         r'\frac{d\text{a}}{d\text{Bob}}')
+
+    def test_partial_derivative(self):
+        self.eq(myokit.PartialDerivative(self.a, self.b),
+                r'\frac{\partial\text{a}}{\partial\text{b}}')
+
+    def test_initial_value(self):
+        self.eq(myokit.InitialValue(self.a), r'\text{a}(\text{t} = 0)')
+
+    def test_prefix_plus(self):
+        # Test with numbers
+        p = Number(11, 'mV')
+        self.eq(PrefixPlus(p), r'+11.0 \text{mV}')
+        p = Number(3)
+        self.eq(PrefixPlus(PrefixPlus(p)), '++3.0')
+        self.eq(PrefixPlus(Number('+1')), '+1.0')
+
+        # Test with operators of precedence SUM, PRODUCT, POWER
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), r'+\left(\text{a}+\text{b}\right)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c),
+                r'\frac{+\left(\text{a}+\text{b}\right)}{\text{c}}')
+        self.eq(Power(PrefixPlus(b), a),
+                r'\left(+\text{b}\right)^\text{a}')
+
+    def test_prefix_minus(self):
+        # Test with numbers
+        p = Number(3, 'uA')
+        self.eq(PrefixMinus(p), r'-3.0 \text{uA}')
+        p = Number(2)
+        self.eq(PrefixMinus(PrefixMinus(p)), '--2.0')
+        self.eq(PrefixMinus(Number('-1')), '--1.0')
+
+        # Test with operators of precedence SUM, PRODUCT, POWER
+        a, b, c = self.abc
+        self.eq(PrefixMinus(Plus(a, b)), r'-\left(\text{a}+\text{b}\right)')
+        self.eq(Divide(PrefixMinus(Plus(a, b)), c),
+                r'\frac{-\left(\text{a}+\text{b}\right)}{\text{c}}')
+        self.eq(Power(PrefixMinus(b), a),
+                r'\left(-\text{b}\right)^\text{a}')
+
+    def test_plus_minus(self):
+        a, b, c = self.abc
+        ta, tb, tc = r'\text{a}', r'\text{b}', r'\text{c}'
+        self.eq(Plus(a, b), f'{ta}+{tb}')
+        self.eq(Plus(Plus(a, b), c), f'{ta}+{tb}+{tc}')
+        self.eq(Plus(a, Plus(b, c)), rf'{ta}+\left({tb}+{tc}\right)')
+
+        self.eq(Minus(a, b), f'{ta}-{tb}')
+        self.eq(Minus(Minus(a, b), c), f'{ta}-{tb}-{tc}')
+        self.eq(Minus(a, Minus(b, c)), rf'{ta}-\left({tb}-{tc}\right)')
+
+        self.eq(Minus(a, b), f'{ta}-{tb}')
+        self.eq(Plus(Minus(a, b), c), f'{ta}-{tb}+{tc}')
+        self.eq(Minus(a, Plus(b, c)), rf'{ta}-\left({tb}+{tc}\right)')
+        self.eq(Minus(Plus(a, b), c), f'{ta}+{tb}-{tc}')
+        self.eq(Minus(a, Plus(b, c)), rf'{ta}-\left({tb}+{tc}\right)')
+
+    def test_multiply_divide(self):
+        a, b, c = self.abc
+        ta, tb, tc = r'\text{a}', r'\text{b}', r'\text{c}'
+        l, r = r'\left(', r'\right)'
+        self.eq(Multiply(a, b), rf'{ta}\cdot{tb}')
+        self.eq(Multiply(Multiply(a, b), c), rf'{ta}\cdot{tb}\cdot{tc}')
+        self.eq(Multiply(a, Multiply(b, c)), rf'{ta}\cdot{l}{tb}\cdot{tc}{r}')
+        self.eq(Divide(a, b), r'\frac{\text{a}}{\text{b}}')
+        self.eq(Divide(Divide(a, b), c),
+                r'\frac{\frac{\text{a}}{\text{b}}}{\text{c}}')
+        self.eq(Divide(a, Divide(b, c)),
+                r'\frac{\text{a}}{\frac{\text{b}}{\text{c}}}')
+        self.eq(Divide(Divide(a, b), c),
+                r'\frac{\frac{\text{a}}{\text{b}}}{\text{c}}')
+        self.eq(Divide(Multiply(a, b), c),
+                r'\frac{\text{a}\cdot\text{b}}{\text{c}}')
+
+        self.eq(Multiply(Minus(a, b), c), rf'{l}{ta}-{tb}{r}\cdot{tc}')
+        self.eq(Multiply(a, Plus(b, c)), rf'{ta}\cdot{l}{tb}+{tc}{r}')
+        self.eq(Plus(a, Multiply(b, c)), rf'{ta}+{tb}\cdot{tc}')
+
+    def test_remainder_quotient(self):
+        a, b = self.ab
+        self.eq(Remainder(a, b), r'\text{a}\bmod\text{b}')
+        self.eq(Quotient(a, b),
+                r'\left\lfloor\frac{\text{a}}{\text{b}}\right\rfloor')
+
+    def test_power(self):
+        a, b, c = self.abc
+        ta, tb, tc = r'\text{a}', r'\text{b}', r'\text{c}'
+        l, r = r'\left(', r'\right)'
+        p, q = '{', '}'
+        self.eq(Power(a, b), f'{ta}^{tb}')
+        self.eq(Power(Power(a, b), c), f'{l}{ta}^{tb}{r}^{tc}')
+        self.eq(Power(a, Power(b, c)), f'{ta}^{p}{tb}^{tc}{q}')
+        self.eq(Power(Plus(a, b), c), f'{l}{ta}+{tb}{r}^{tc}')
+        self.eq(Power(a, Minus(b, c)), f'{ta}^{p}{tb}-{tc}{q}')
+
+    def test_functions(self):
+        a, b = self.ab
+
+        self.eq(Sqrt(a), r'\sqrt{\text{a}}')
+        self.eq(Exp(a), r'\exp\left(\text{a}\right)')
+        self.eq(Log(a), r'\log\left(\text{a}\right)')
+        self.eq(Log(a, b), r'\log_{\text{b}}\left(\text{a}\right)')
+        self.eq(Log10(a), r'\log_{10}\left(\text{a}\right)')
+        self.eq(Sin(a), r'\sin\left(\text{a}\right)')
+        self.eq(Cos(a), r'\cos\left(\text{a}\right)')
+        self.eq(Tan(a), r'\tan\left(\text{a}\right)')
+        self.eq(ASin(a), r'\arcsin\left(\text{a}\right)')
+        self.eq(ACos(a), r'\arccos\left(\text{a}\right)')
+        self.eq(ATan(a), r'\arctan\left(\text{a}\right)')
+        self.eq(Floor(a), r'\left\lfloor{\text{a}}\right\rfloor')
+        self.eq(Ceil(a), r'\left\lceil{\text{a}}\right\rceil')
+        self.eq(Abs(a), r'\lvert{\text{a}}\rvert')
+
+    def test_conditions(self):
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), r'\left(\text{a}\and\text{b}\right)')
+        self.eq(Or(d, c), r'\left(\text{d}\or\text{c}\right)')
+        self.eq(Not(c), r'\left(\not\text{c}\right)')
+
+        self.eq(Equal(a, b), r'\left(\text{a}=\text{b}\right)')
+        self.eq(NotEqual(a, b), r'\left(\text{a}\neq\text{b}\right)')
+        self.eq(More(b, a), r'\left(\text{b}>\text{a}\right)')
+        self.eq(Less(d, c), r'\left(\text{d}<\text{c}\right)')
+        self.eq(MoreEqual(c, a), r'\left(\text{c}\geq\text{a}\right)')
+        self.eq(LessEqual(b, d), r'\left(\text{b}\leq\text{d}\right)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)),
+                r'\left(\left(\text{a}=\text{b}\right)\and'
+                r'\left(\text{c}\neq\text{d}\right)\right)')
+        self.eq(Not(Equal(d, d)),
+                r'\left(\not\left(\text{d}=\text{d}\right)\right)')
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                r'\left(\left(0.0=0.0\right)=0.0\right)')
+
+    def test_conditionals(self):
+
+        a, b, c, d = self.abcd
+        self.eq(myokit.If(a, b, c),
+                r'\text{if}\left(\text{a},\text{b},\text{c}\right)')
+        self.eq(myokit.Piecewise(a, b, c, d, Number(1)), r'\text{piecewise}'
+                r'\left(\text{a},\text{b},\text{c},\text{d},1.0\right)')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_mathml_content.py b/myokit/tests/test_formats_mathml_content.py
index cf42c831e..a3e610fe1 100755
--- a/myokit/tests/test_formats_mathml_content.py
+++ b/myokit/tests/test_formats_mathml_content.py
@@ -82,6 +82,10 @@ def test_arithmetic_unary(self):
         e = myokit.PrefixPlus(myokit.Number(1))
         x = '<apply><plus/><cn>1.0</cn></apply>'
         self.assertEqual(self.p(x), e)
+        e = myokit.PrefixPlus(myokit.Plus(myokit.Number(3), myokit.Number(4)))
+        x = ('<apply><plus/><apply><plus /><cn>3.0</cn><cn>4.0</cn></apply>'
+             '</apply>')
+        self.assertEqual(self.p(x), e)
 
         # Prefix minus
         e = myokit.PrefixMinus(myokit.Number(1))
@@ -358,9 +362,18 @@ def test_functions(self):
         # Power
         a = myokit.Name('a')
         b = myokit.Number(1)
+        c = myokit.Number(2)
         e = myokit.Power(a, b)
         x = '<apply><power/><ci>a</ci><cn>1.0</cn></apply>'
         self.assertEqual(self.p(x), e)
+        e = myokit.Power(myokit.Power(a, b), c)
+        x = ('<apply><power/><apply><power/><ci>a</ci><cn>1.0</cn></apply>'
+             '<cn>2.0</cn></apply>')
+        self.assertEqual(self.p(x), e)
+        e = myokit.Power(a, myokit.Power(b, c))
+        x = ('<apply><power/><ci>a</ci><apply><power/><cn>1.0</cn><cn>2.0</cn>'
+             '</apply></apply>')
+        self.assertEqual(self.p(x), e)
 
         #TODO: Degree etc.
 
diff --git a/myokit/tests/test_formats_mathml_presentation.py b/myokit/tests/test_formats_mathml_presentation.py
index 206d9efbc..34785e2ab 100755
--- a/myokit/tests/test_formats_mathml_presentation.py
+++ b/myokit/tests/test_formats_mathml_presentation.py
@@ -22,6 +22,7 @@ def setUpClass(cls):
         model = myokit.Model()
         component = model.add_component('c')
         cls.avar = component.add_variable('a')
+        cls.bvar = component.add_variable('b')
 
         cls.w = mathml.MathMLExpressionWriter()
         cls.w.set_mode(presentation=True)
@@ -46,33 +47,40 @@ def test_arithmetic_binary(self):
 
         # Plus
         x = myokit.Plus(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>+</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>+</mo>{cb}</mrow>')
 
         # Minus
         x = myokit.Minus(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>-</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>-</mo>{cb}</mrow>')
 
         # Multiply
         x = myokit.Multiply(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>*</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>*</mo>{cb}</mrow>')
 
         # Divide
         x = myokit.Divide(a, b)
-        self.assertWrite(x, '<mfrac>' + ca + cb + '</mfrac>')
+        self.assertWrite(x, f'<mfrac>{ca + cb}</mfrac>')
 
     def test_arithmetic_unary(self):
         # Tests writing prefix operators
 
+        a = myokit.Name(self.avar)
         b = myokit.Number('12', 'pF')
+        ca = '<mi>c.a</mi>'
         cb = '<mn>12.0</mn>'
 
         # Prefix plus
         x = myokit.PrefixPlus(b)
-        self.assertWrite(x, '<mrow><mo>+</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow><mo>+</mo>{cb}</mrow>')
+        x = myokit.Divide(myokit.PrefixPlus(myokit.Plus(a, b)), a)
+        self.assertWrite(
+            x,
+            f'<mfrac><mrow><mo>+</mo><mo>(</mo><mrow>{ca}<mo>+</mo>{cb}</mrow>'
+            f'<mo>)</mo></mrow>{ca}</mfrac>')
 
         # Prefix minus
         x = myokit.PrefixMinus(b)
-        self.assertWrite(x, '<mrow><mo>-</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow><mo>-</mo>{cb}</mrow>')
 
     def test_conditionals(self):
         # Tests if and piecewise writing
@@ -94,8 +102,8 @@ def test_conditionals(self):
         self.assertWrite(
             x,
             '<piecewise>'
-            '<piece>' + ca + c1 + '</piece>'
-            '<otherwise>' + cb + '</otherwise>'
+            f'<piece>{ca + c1}</piece>'
+            f'<otherwise>{cb}</otherwise>'
             '</piecewise>'
         )
         # Piecewise
@@ -103,9 +111,9 @@ def test_conditionals(self):
         self.assertWrite(
             x,
             '<piecewise>'
-            '<piece>' + ca + c1 + '</piece>'
-            '<piece>' + cb + c2 + '</piece>'
-            '<otherwise>' + cc + '</otherwise>'
+            f'<piece>{ca + c1}</piece>'
+            f'<piece>{cb + c2}</piece>'
+            f'<otherwise>{cc}</otherwise>'
             '</piecewise>'
         )
 
@@ -119,63 +127,69 @@ def test_functions(self):
         # Tests writing basic functions
 
         a = myokit.Name(self.avar)
-        b = myokit.Number('12', 'pF')
+        b = myokit.Name(self.bvar)
+        c = myokit.Number('12', 'pF')
         ca = '<mi>c.a</mi>'
-        cb = '<mn>12.0</mn>'
+        cb = '<mi>c.b</mi>'
+        cc = '<mn>12.0</mn>'
 
         # Power
         x = myokit.Power(a, b)
-        self.assertWrite(x, '<msup>' + ca + cb + '</msup>')
+        self.assertWrite(x, f'<msup>{ca}{cb}</msup>')
+        x = myokit.Power(myokit.Power(a, b), c)
+        self.assertWrite(x, f'<msup><msup>{ca}{cb}</msup>{cc}</msup>')
+        x = myokit.Power(a, myokit.Power(b, c))
+        self.assertWrite(x, f'<msup>{ca}<msup>{cb}{cc}</msup></msup>')
 
         # Sqrt
         x = myokit.Sqrt(b)
         self.assertWrite(
-            x, '<mrow><mi>root</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>root</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Exp
         x = myokit.Exp(a)
-        self.assertWrite(x, '<msup><mi>e</mi>' + ca + '</msup>')
+        self.assertWrite(x, f'<msup><mi>e</mi>{ca}</msup>')
 
         # Log(a)
         x = myokit.Log(b)
         self.assertWrite(
-            x, '<mrow><mi>ln</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>ln</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Log(a, b)
         x = myokit.Log(a, b)
         self.assertWrite(
             x,
-            '<mrow><msub><mi>log</mi>' + cb + '</msub>'
-            '<mfenced>' + ca + '</mfenced></mrow>'
+            f'<mrow><msub><mi>log</mi>{cb}</msub>'
+            f'<mfenced>{ca}</mfenced></mrow>'
         )
 
         # Log10
         x = myokit.Log10(b)
         self.assertWrite(
-            x, '<mrow><mi>log</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>log</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Floor
         x = myokit.Floor(b)
         self.assertWrite(
-            x, '<mrow><mi>floor</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>floor</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Ceil
         x = myokit.Ceil(b)
         self.assertWrite(
-            x, '<mrow><mi>ceiling</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>ceiling</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Abs
         x = myokit.Abs(b)
         self.assertWrite(
-            x, '<mrow><mi>abs</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>abs</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Quotient
         x = myokit.Quotient(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>//</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>//</mo>{cb}</mrow>')
 
         # Remainder
         x = myokit.Remainder(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>%</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>%</mo>{cb}</mrow>')
 
     def test_inequalities(self):
         # Test writing (in)equalities
@@ -187,31 +201,29 @@ def test_inequalities(self):
 
         # Equal
         x = myokit.Equal(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>==</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>==</mo>{cb}</mrow>')
 
         # NotEqual
         x = myokit.NotEqual(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>!=</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>!=</mo>{cb}</mrow>')
 
         # More
         x = myokit.More(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>&gt;</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>&gt;</mo>{cb}</mrow>')
 
         # Less
         x = myokit.Less(a, b)
-        self.assertWrite(x, '<mrow>' + ca + '<mo>&lt;</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>&lt;</mo>{cb}</mrow>')
 
         # MoreEqual
         # Named version &ge; is not output, shows decimal code instead
         x = myokit.MoreEqual(a, b)
-        self.assertWrite(
-            x, '<mrow>' + ca + '<mo>&#8805;</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>&#8805;</mo>{cb}</mrow>')
 
         # LessEqual
         # Named version &le; is not output, shows decimal code instead
         x = myokit.LessEqual(a, b)
-        self.assertWrite(
-            x, '<mrow>' + ca + '<mo>&#8804;</mo>' + cb + '</mrow>')
+        self.assertWrite(x, f'<mrow>{ca}<mo>&#8804;</mo>{cb}</mrow>')
 
     def test_logic_operators(self):
         # Tests writing logic operators
@@ -224,15 +236,15 @@ def test_logic_operators(self):
         # Not
         x = myokit.Not(cond1)
         self.assertWrite(
-            x, '<mrow><mo>(</mo><mo>not</mo>' + c1 + '<mo>)</mo></mrow>')
+            x, f'<mrow><mo>not</mo><mo>(</mo>{c1}<mo>)</mo></mrow>')
 
         # And
         x = myokit.And(cond1, cond2)
-        self.assertWrite(x, '<mrow>' + c1 + '<mo>and</mo>' + c2 + '</mrow>')
+        self.assertWrite(x, f'<mrow>{c1}<mo>and</mo>{c2}</mrow>')
 
         # Or
         x = myokit.Or(cond1, cond2)
-        self.assertWrite(x, '<mrow>' + c1 + '<mo>or</mo>' + c2 + '</mrow>')
+        self.assertWrite(x, f'<mrow>{c1}<mo>or</mo>{c2}</mrow>')
 
     def test_name_and_numbers(self):
         # Test name and number writing
@@ -259,32 +271,32 @@ def test_trig_basic(self):
         # Sin
         x = myokit.Sin(b)
         self.assertWrite(
-            x, '<mrow><mi>sin</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>sin</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Cos
         x = myokit.Cos(b)
         self.assertWrite(
-            x, '<mrow><mi>cos</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>cos</mi><mfenced>{cb}</mfenced></mrow>')
 
         # Tan
         x = myokit.Tan(b)
         self.assertWrite(
-            x, '<mrow><mi>tan</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>tan</mi><mfenced>{cb}</mfenced></mrow>')
 
         # ASin
         x = myokit.ASin(b)
         self.assertWrite(
-            x, '<mrow><mi>arcsin</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>arcsin</mi><mfenced>{cb}</mfenced></mrow>')
 
         # ACos
         x = myokit.ACos(b)
         self.assertWrite(
-            x, '<mrow><mi>arccos</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>arccos</mi><mfenced>{cb}</mfenced></mrow>')
 
         # ATan
         x = myokit.ATan(b)
         self.assertWrite(
-            x, '<mrow><mi>arctan</mi><mfenced>' + cb + '</mfenced></mrow>')
+            x, f'<mrow><mi>arctan</mi><mfenced>{cb}</mfenced></mrow>')
 
     def test_unknown_expression(self):
         # Test without a Myokit expression
diff --git a/myokit/tests/test_formats_matlab.py b/myokit/tests/test_formats_matlab.py
new file mode 100755
index 000000000..4283b521b
--- /dev/null
+++ b/myokit/tests/test_formats_matlab.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+#
+# Tests the expression writer for Matlab.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import unittest
+
+import myokit
+import myokit.formats.matlab
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
+
+import myokit.tests
+
+
+class MatlabExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    _name = 'matlab'
+    _target = myokit.formats.matlab.MatlabExpressionWriter
+
+    def test_number(self):
+        self.eq(Number(1), '1.0')
+        self.eq(Number(-2), '-2.0')
+        self.eq(Number(13, 'mV'), '13.0')
+
+    def test_name(self):
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        e = myokit.PartialDerivative(self.a, self.b)
+        self.assertRaisesRegex(NotImplementedError, 'Partial', self.w.ex, e)
+
+    def test_initial_value(self):
+        e = myokit.InitialValue(self.a)
+        self.assertRaisesRegex(NotImplementedError, 'Initial', self.w.ex, e)
+
+    def test_prefix_plus(self):
+        # Inherited from Python writer
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0')
+        self.eq(PrefixPlus(PrefixPlus(p)), '++11.0')
+        self.eq(PrefixPlus(Number('+1')), '+1.0')
+
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), '+(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(Power(PrefixPlus(a), b), '(+a)^b')
+
+    def test_prefix_minus(self):
+        # Inherited from Python writer
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixMinus(p), '-11.0')
+        self.eq(PrefixMinus(PrefixMinus(p)), '--11.0')
+        self.eq(PrefixMinus(Number(-1)), '--1.0')
+
+        a, b, c = self.abc
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(Power(PrefixMinus(a), b), '(-a)^b')
+
+    def test_plus_minus(self):
+        # Inherited from Python writer
+        a, b, c = self.abc
+        self.eq(Plus(a, b), 'a + b')
+        self.eq(Plus(Plus(a, b), c), 'a + b + c')
+        self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Minus(Minus(a, b), c), 'a - b - c')
+        self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Plus(Minus(a, b), c), 'a - b + c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+
+    def test_multiply_divide(self):
+        # Inherited from Python writer
+
+        a, b, c = self.abc
+        self.eq(Multiply(a, b), 'a * b')
+        # Left-to-right, so (a * b) * c is the same as a * b * c...
+        self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
+        # ...but order-of-operations-wise, a * (b * c) is different!
+        self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
+        # Note that a user typing a * b * c results in (a * b) * c
+
+        self.eq(Divide(a, b), 'a / b')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+
+        self.eq(Divide(Multiply(a, b), c), 'a * b / c')
+        self.eq(Multiply(Divide(a, b), c), 'a / b * c')
+        self.eq(Divide(a, Multiply(b, c)), 'a / (b * c)')
+        self.eq(Multiply(a, Divide(b, c)), 'a * (b / c)')
+
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Multiply(a, Plus(b, c)), 'a * (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Plus(a, Multiply(b, c)), 'a + b * c')
+        self.eq(Divide(Plus(a, b), c), '(a + b) / c')
+        self.eq(Divide(a, Minus(b, c)), 'a / (b - c)')
+        self.eq(Plus(Divide(a, b), c), 'a / b + c')
+        self.eq(Minus(a, Divide(b, c)), 'a - b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+
+    def test_quotient(self):
+        a, b, c = self.abc
+        self.eq(Quotient(a, b), 'floor(a / b)')
+        self.eq(Quotient(Plus(a, c), b), 'floor((a + c) / b)')
+        self.eq(Quotient(Divide(a, c), b), 'floor(a / c / b)')
+        self.eq(Quotient(a, Divide(b, c)), 'floor(a / (b / c))')
+        self.eq(Multiply(Quotient(a, b), c), 'floor(a / b) * c')
+        # Bracket() method expects a PRODUCT level operation, so will add
+        # unnecessary brackets here
+        self.eq(Multiply(c, Quotient(a, b)), 'c * (floor(a / b))')
+
+    def test_remainder(self):
+        a, b, c = self.abc
+        self.eq(Remainder(a, b), 'mod(a, b)')
+        self.eq(Remainder(Plus(a, c), b), 'mod(a + c, b)')
+        self.eq(Multiply(Remainder(a, b), c), 'mod(a, b) * c')
+        # Bracket() method expects a PRODUCT level operation, so will add
+        # unnecessary brackets here
+        self.eq(Divide(c, Remainder(b, a)), 'c / (mod(b, a))')
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'a^b')
+
+        # Like Myokit, Matlab sees a^b^c as (a^b)^c
+        self.eq(Power(Power(a, b), c), 'a^b^c')
+        self.eq(Power(a, Power(b, c)), 'a^(b^c)')
+
+        self.eq(Power(Plus(a, b), c), '(a + b)^c')
+        self.eq(Power(a, Minus(b, c)), 'a^(b - c)')
+        self.eq(Power(Multiply(a, b), c), '(a * b)^c')
+        self.eq(Power(a, Divide(b, c)), 'a^(b / c)')
+
+    def test_log(self):
+        a, b = self.ab
+        self.eq(Log(a), 'log(a)')
+        self.eq(Log10(a), 'log10(a)')
+        self.eq(Log(a, b), '(log(a) / log(b))')
+
+    def test_functions(self):
+        a, b = self.ab
+        self.eq(Sqrt(a), 'sqrt(a)')
+        self.eq(Exp(a), 'exp(a)')
+        self.eq(Sin(a), 'sin(a)')
+        self.eq(Cos(a), 'cos(a)')
+        self.eq(Tan(a), 'tan(a)')
+        self.eq(ASin(a), 'asin(a)')
+        self.eq(ACos(a), 'acos(a)')
+        self.eq(ATan(a), 'atan(a)')
+        self.eq(Floor(a), 'floor(a)')
+        self.eq(Ceil(a), 'ceil(a)')
+        self.eq(Abs(a), 'abs(a)')
+
+    def test_conditions(self):
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), '(a && b)')
+        self.eq(Or(d, c), '(d || c)')
+        self.eq(Not(c), '(!c)')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)), '((d > c) || (b < a))')
+        self.eq(Not(Equal(d, d)), '(!(d == d))')
+        self.eq(Not(Or(Number(1), Number(2))), '(!(1.0 || 2.0))')
+
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0 == 0.0) == 0.0)')
+
+    def test_conditionals(self):
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), 'ifthenelse((a == b), d, c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a), 'ifthenelse((d != c), b, a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                'ifthenelse((a == b), c, ifthenelse((a == d), 3.0, 4.0))')
+
+    def test_unset_condition_function(self):
+        # No ternary operator, so matlab must always have an ifthenelse
+        w = self._target()
+        self.assertRaisesRegex(
+            ValueError, 'needs a condition function',
+            w.set_condition_function, None)
+        self.assertRaisesRegex(
+            ValueError, 'needs a condition function',
+            w.set_condition_function, '')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_opencl.py b/myokit/tests/test_formats_opencl.py
index 88c185e9e..92dca353f 100755
--- a/myokit/tests/test_formats_opencl.py
+++ b/myokit/tests/test_formats_opencl.py
@@ -8,392 +8,218 @@
 import unittest
 
 import myokit
-import myokit.formats
 import myokit.formats.opencl
 
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
+
+import myokit.tests
+
+
+class OpenCLExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test conversion to Ansi C, as used by the Simulation.
+    Numerical tests are provided by composing and evaluating a single RHS.
+    """
+    _name = 'opencl'
+    _target = myokit.formats.opencl.OpenCLExpressionWriter
+
+    def test_number(self):
+        self.eq(Number(1), '1.0f')
+        self.eq(Number(-2), '-2.0f')
+        self.eq(Number(13, 'mV'), '13.0f')
+
+    def test_number_double(self):
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        self.assertEqual(w.ex(Number(1)), '1.0')
+        self.assertEqual(w.ex(Number(-2)), '-2.0')
+        self.assertEqual(w.ex(Number(13, 'mV')), '13.0')
+
+    def test_name(self):
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        self.assertRaisesRegex(
+            NotImplementedError, 'Partial',
+            self.w.ex, myokit.PartialDerivative(self.a, self.b))
 
-class OpenCLExpressionWriterTest(unittest.TestCase):
-    """ Test the OpenCL ewriter class. """
-
-    def test_basic(self):
-
-        # Single and double precision and native maths
-        ws = myokit.formats.opencl.OpenCLExpressionWriter()
-        wd = myokit.formats.opencl.OpenCLExpressionWriter(
-            myokit.DOUBLE_PRECISION)
-        wn = myokit.formats.opencl.OpenCLExpressionWriter(native_math=False)
-
-        a = myokit.Name(myokit.Model().add_component('c').add_variable('a'))
-        b = myokit.Number('12', 'pF')
-
-        # Name
-        self.assertEqual(ws.ex(a), 'c.a')
-        self.assertEqual(wd.ex(a), 'c.a')
-        self.assertEqual(wn.ex(a), 'c.a')
-
-        # Number with unit
-        self.assertEqual(ws.ex(b), '12.0f')
-        self.assertEqual(wd.ex(b), '12.0')
-        self.assertEqual(wn.ex(b), '12.0f')
-
-        # Prefix plus
-        x = myokit.PrefixPlus(b)
-        self.assertEqual(ws.ex(x), '12.0f')
-        self.assertEqual(wd.ex(x), '12.0')
-        self.assertEqual(wn.ex(x), '12.0f')
-        # Prefix minus
-        x = myokit.PrefixMinus(b)
-        self.assertEqual(ws.ex(x), '(-12.0f)')
-        self.assertEqual(wd.ex(x), '(-12.0)')
-        self.assertEqual(wn.ex(x), '(-12.0f)')
-
-        # Plus
-        x = myokit.Plus(a, b)
-        self.assertEqual(ws.ex(x), 'c.a + 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a + 12.0')
-        self.assertEqual(wn.ex(x), 'c.a + 12.0f')
-        # Minus
-        x = myokit.Minus(a, b)
-        self.assertEqual(ws.ex(x), 'c.a - 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a - 12.0')
-        self.assertEqual(wn.ex(x), 'c.a - 12.0f')
-        # Multiply
-        x = myokit.Multiply(a, b)
-        self.assertEqual(ws.ex(x), 'c.a * 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a * 12.0')
-        self.assertEqual(wn.ex(x), 'c.a * 12.0f')
-        # Divide
-        x = myokit.Divide(a, b)
-        self.assertEqual(ws.ex(x), 'c.a / 12.0f')
-        self.assertEqual(wd.ex(x), 'c.a / 12.0')
-        self.assertEqual(wn.ex(x), 'c.a / 12.0f')
-
-        # Quotient
-        x = myokit.Quotient(a, b)
-        self.assertEqual(ws.ex(x), 'floor(c.a / 12.0f)')
-        self.assertEqual(wd.ex(x), 'floor(c.a / 12.0)')
-        self.assertEqual(wn.ex(x), 'floor(c.a / 12.0f)')
-        # Remainder
-        x = myokit.Remainder(a, b)
-        self.assertEqual(ws.ex(x), 'c.a - 12.0f * (floor(c.a / 12.0f))')
-        self.assertEqual(wd.ex(x), 'c.a - 12.0 * (floor(c.a / 12.0))')
-        self.assertEqual(wn.ex(x), 'c.a - 12.0f * (floor(c.a / 12.0f))')
-
-    def test_functions(self):
-
-        # Single and double precision and native maths
-        ws = myokit.formats.opencl.OpenCLExpressionWriter()
-        wd = myokit.formats.opencl.OpenCLExpressionWriter(
-            myokit.DOUBLE_PRECISION)
-        wn = myokit.formats.opencl.OpenCLExpressionWriter(native_math=False)
-
-        a = myokit.Name(myokit.Model().add_component('c').add_variable('a'))
-        b = myokit.Number('12', 'pF')
-
-        # Power
-        x = myokit.Power(a, b)
-        self.assertEqual(ws.ex(x), 'pow(c.a, 12.0f)')
-        self.assertEqual(wd.ex(x), 'pow(c.a, 12.0)')
-        self.assertEqual(wn.ex(x), 'pow(c.a, 12.0f)')
-        # Square
-        x = myokit.Power(a, myokit.Number(2))
-        self.assertEqual(ws.ex(x), '(c.a * c.a)')
-        self.assertEqual(wd.ex(x), '(c.a * c.a)')
-        self.assertEqual(wn.ex(x), '(c.a * c.a)')
-        # Square with brackets
-        x = myokit.Power(myokit.Plus(a, b), myokit.Number(2))
-        self.assertEqual(ws.ex(x), '((c.a + 12.0f) * (c.a + 12.0f))')
-        self.assertEqual(wd.ex(x), '((c.a + 12.0) * (c.a + 12.0))')
-        self.assertEqual(wn.ex(x), '((c.a + 12.0f) * (c.a + 12.0f))')
-        # Sqrt
-        x = myokit.Sqrt(b)
-        self.assertEqual(ws.ex(x), 'native_sqrt(12.0f)')
-        self.assertEqual(wd.ex(x), 'native_sqrt(12.0)')
-        self.assertEqual(wn.ex(x), 'sqrt(12.0f)')
-        # Exp
-        x = myokit.Exp(a)
-        self.assertEqual(ws.ex(x), 'native_exp(c.a)')
-        self.assertEqual(wd.ex(x), 'native_exp(c.a)')
-        self.assertEqual(wn.ex(x), 'exp(c.a)')
-        # Log(a)
-        x = myokit.Log(b)
-        self.assertEqual(ws.ex(x), 'native_log(12.0f)')
-        self.assertEqual(wd.ex(x), 'native_log(12.0)')
-        self.assertEqual(wn.ex(x), 'log(12.0f)')
-        # Log(a, b)
-        x = myokit.Log(a, b)
-        self.assertEqual(ws.ex(x), '(native_log(c.a) / native_log(12.0f))')
-        self.assertEqual(wd.ex(x), '(native_log(c.a) / native_log(12.0))')
-        self.assertEqual(wn.ex(x), '(log(c.a) / log(12.0f))')
-        # Log10
-        x = myokit.Log10(b)
-        self.assertEqual(ws.ex(x), 'native_log10(12.0f)')
-        self.assertEqual(wd.ex(x), 'native_log10(12.0)')
-        self.assertEqual(wn.ex(x), 'log10(12.0f)')
-
-        # Sin
-        x = myokit.Sin(b)
-        self.assertEqual(ws.ex(x), 'native_sin(12.0f)')
-        self.assertEqual(wd.ex(x), 'native_sin(12.0)')
-        self.assertEqual(wn.ex(x), 'sin(12.0f)')
-        # Cos
-        x = myokit.Cos(b)
-        self.assertEqual(ws.ex(x), 'native_cos(12.0f)')
-        self.assertEqual(wd.ex(x), 'native_cos(12.0)')
-        self.assertEqual(wn.ex(x), 'cos(12.0f)')
-        # Tan
-        x = myokit.Tan(b)
-        self.assertEqual(ws.ex(x), 'native_tan(12.0f)')
-        self.assertEqual(wd.ex(x), 'native_tan(12.0)')
-        self.assertEqual(wn.ex(x), 'tan(12.0f)')
-        # ASin
-        x = myokit.ASin(b)
-        self.assertEqual(ws.ex(x), 'asin(12.0f)')
-        self.assertEqual(wd.ex(x), 'asin(12.0)')
-        self.assertEqual(wn.ex(x), 'asin(12.0f)')
-        # ACos
-        x = myokit.ACos(b)
-        self.assertEqual(ws.ex(x), 'acos(12.0f)')
-        self.assertEqual(wd.ex(x), 'acos(12.0)')
-        self.assertEqual(wn.ex(x), 'acos(12.0f)')
-        # ATan
-        x = myokit.ATan(b)
-        self.assertEqual(ws.ex(x), 'atan(12.0f)')
-        self.assertEqual(wd.ex(x), 'atan(12.0)')
-        self.assertEqual(wn.ex(x), 'atan(12.0f)')
-
-        # Floor
-        x = myokit.Floor(b)
-        self.assertEqual(ws.ex(x), 'floor(12.0f)')
-        self.assertEqual(wd.ex(x), 'floor(12.0)')
-        self.assertEqual(wn.ex(x), 'floor(12.0f)')
-        # Ceil
-        x = myokit.Ceil(b)
-        self.assertEqual(ws.ex(x), 'ceil(12.0f)')
-        self.assertEqual(wd.ex(x), 'ceil(12.0)')
-        self.assertEqual(wn.ex(x), 'ceil(12.0f)')
-        # Abs
-        x = myokit.Abs(b)
-        self.assertEqual(ws.ex(x), 'fabs(12.0f)')
-        self.assertEqual(wd.ex(x), 'fabs(12.0)')
-        self.assertEqual(wn.ex(x), 'fabs(12.0f)')
-
-    def test_conditional(self):
-
-        # Single and double precision and native maths
-        ws = myokit.formats.opencl.OpenCLExpressionWriter()
-        wd = myokit.formats.opencl.OpenCLExpressionWriter(
-            myokit.DOUBLE_PRECISION)
-        wn = myokit.formats.opencl.OpenCLExpressionWriter(native_math=False)
-
-        a = myokit.Name(myokit.Model().add_component('c').add_variable('a'))
-        b = myokit.Number('12', 'pF')
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-
-        # Equal
-        x = myokit.Equal(a, b)
-        self.assertEqual(ws.ex(x), '(c.a == 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a == 12.0)')
-        self.assertEqual(wn.ex(x), '(c.a == 12.0f)')
-        x = myokit.Equal(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) == (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) == (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) == (2.0f < 1.0f))')
-        x = myokit.Equal(cond1, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) == (12.0f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) == (12.0 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) == (12.0f != 0.0f))')
-        x = myokit.Equal(a, cond2)
-        self.assertEqual(ws.ex(x), '((c.a != 0.0f) == (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((c.a != 0.0) == (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((c.a != 0.0f) == (2.0f < 1.0f))')
-
-        # NotEqual
-        x = myokit.NotEqual(a, b)
-        self.assertEqual(ws.ex(x), '(c.a != 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a != 12.0)')
-        self.assertEqual(wn.ex(x), '(c.a != 12.0f)')
-        x = myokit.NotEqual(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) != (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) != (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) != (2.0f < 1.0f))')
-        x = myokit.NotEqual(cond1, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) != (12.0f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) != (12.0 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) != (12.0f != 0.0f))')
-        x = myokit.NotEqual(a, cond2)
-        self.assertEqual(ws.ex(x), '((c.a != 0.0f) != (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((c.a != 0.0) != (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((c.a != 0.0f) != (2.0f < 1.0f))')
-
-        # More
-        x = myokit.More(a, b)
-        self.assertEqual(ws.ex(x), '(c.a > 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a > 12.0)')
-        self.assertEqual(wn.ex(x), '(c.a > 12.0f)')
-        x = myokit.More(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) > (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) > (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) > (2.0f < 1.0f))')
-        x = myokit.More(cond1, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) > (12.0f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) > (12.0 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) > (12.0f != 0.0f))')
-        x = myokit.More(a, cond2)
-        self.assertEqual(ws.ex(x), '((c.a != 0.0f) > (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((c.a != 0.0) > (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((c.a != 0.0f) > (2.0f < 1.0f))')
-
-        # Less
-        x = myokit.Less(a, b)
-        self.assertEqual(ws.ex(x), '(c.a < 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a < 12.0)')
-        self.assertEqual(wn.ex(x), '(c.a < 12.0f)')
-        x = myokit.Less(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) < (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) < (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) < (2.0f < 1.0f))')
-        x = myokit.Less(cond1, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) < (12.0f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) < (12.0 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) < (12.0f != 0.0f))')
-        x = myokit.Less(a, cond2)
-        self.assertEqual(ws.ex(x), '((c.a != 0.0f) < (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((c.a != 0.0) < (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((c.a != 0.0f) < (2.0f < 1.0f))')
-
-        # MoreEqual
-        x = myokit.MoreEqual(a, b)
-        self.assertEqual(ws.ex(x), '(c.a >= 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a >= 12.0)')
-        self.assertEqual(wn.ex(x), '(c.a >= 12.0f)')
-        x = myokit.MoreEqual(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) >= (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) >= (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) >= (2.0f < 1.0f))')
-        x = myokit.MoreEqual(cond1, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) >= (12.0f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) >= (12.0 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) >= (12.0f != 0.0f))')
-        x = myokit.MoreEqual(a, cond2)
-        self.assertEqual(ws.ex(x), '((c.a != 0.0f) >= (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((c.a != 0.0) >= (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((c.a != 0.0f) >= (2.0f < 1.0f))')
-
-        # LessEqual
-        x = myokit.LessEqual(a, b)
-        self.assertEqual(ws.ex(x), '(c.a <= 12.0f)')
-        self.assertEqual(wd.ex(x), '(c.a <= 12.0)')
-        self.assertEqual(wn.ex(x), '(c.a <= 12.0f)')
-        x = myokit.LessEqual(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) <= (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) <= (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) <= (2.0f < 1.0f))')
-        x = myokit.LessEqual(cond1, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) <= (12.0f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) <= (12.0 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) <= (12.0f != 0.0f))')
-        x = myokit.LessEqual(a, cond2)
-        self.assertEqual(ws.ex(x), '((c.a != 0.0f) <= (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((c.a != 0.0) <= (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((c.a != 0.0f) <= (2.0f < 1.0f))')
-
-    def test_logical(self):
-
-        # Single and double precision and native maths
-        ws = myokit.formats.opencl.OpenCLExpressionWriter()
-        wd = myokit.formats.opencl.OpenCLExpressionWriter(
-            myokit.DOUBLE_PRECISION)
-        wn = myokit.formats.opencl.OpenCLExpressionWriter(native_math=False)
-
-        a = myokit.Name(myokit.Model().add_component('c').add_variable('a'))
-        b = myokit.Number('12', 'pF')
-        cond1 = myokit.parse_expression('5 > 3')
-        cond2 = myokit.parse_expression('2 < 1')
-        condx = myokit.Number(1.2)
-
-        # Not
-        x = myokit.Not(cond1)
-        self.assertEqual(ws.ex(x), '!((5.0f > 3.0f))')
-        self.assertEqual(wd.ex(x), '!((5.0 > 3.0))')
-        self.assertEqual(wn.ex(x), '!((5.0f > 3.0f))')
-        x = myokit.Not(condx)
-        self.assertEqual(ws.ex(x), '!((1.2f != 0.0f))')
-        self.assertEqual(wd.ex(x), '!((1.2 != 0.0))')
-        self.assertEqual(wn.ex(x), '!((1.2f != 0.0f))')
-
-        # And
-        x = myokit.And(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) && (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) && (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) && (2.0f < 1.0f))')
-        x = myokit.And(condx, cond2)
-        self.assertEqual(ws.ex(x), '((1.2f != 0.0f) && (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((1.2 != 0.0) && (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((1.2f != 0.0f) && (2.0f < 1.0f))')
-        x = myokit.And(cond1, condx)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) && (1.2f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) && (1.2 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) && (1.2f != 0.0f))')
-
-        # Or
-        x = myokit.Or(cond1, cond2)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) || (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) || (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) || (2.0f < 1.0f))')
-        x = myokit.Or(condx, cond2)
-        self.assertEqual(ws.ex(x), '((1.2f != 0.0f) || (2.0f < 1.0f))')
-        self.assertEqual(wd.ex(x), '((1.2 != 0.0) || (2.0 < 1.0))')
-        self.assertEqual(wn.ex(x), '((1.2f != 0.0f) || (2.0f < 1.0f))')
-        x = myokit.Or(cond1, condx)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) || (1.2f != 0.0f))')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) || (1.2 != 0.0))')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) || (1.2f != 0.0f))')
-
-        # If
-        x = myokit.If(cond1, a, b)
-        self.assertEqual(ws.ex(x), '((5.0f > 3.0f) ? c.a : 12.0f)')
-        self.assertEqual(wd.ex(x), '((5.0 > 3.0) ? c.a : 12.0)')
-        self.assertEqual(wn.ex(x), '((5.0f > 3.0f) ? c.a : 12.0f)')
-        x = myokit.If(condx, a, b)
-        self.assertEqual(ws.ex(x), '((1.2f != 0.0f) ? c.a : 12.0f)')
-        self.assertEqual(wd.ex(x), '((1.2 != 0.0) ? c.a : 12.0)')
-        self.assertEqual(wn.ex(x), '((1.2f != 0.0f) ? c.a : 12.0f)')
-
-        # Piecewise
-        c = myokit.Number(1)
-        x = myokit.Piecewise(cond1, a, cond2, b, c)
-        self.assertEqual(
-            ws.ex(x),
-            '((5.0f > 3.0f) ? c.a : ((2.0f < 1.0f) ? 12.0f : 1.0f))')
-        self.assertEqual(
-            wd.ex(x),
-            '((5.0 > 3.0) ? c.a : ((2.0 < 1.0) ? 12.0 : 1.0))')
-        self.assertEqual(
-            wn.ex(x),
-            '((5.0f > 3.0f) ? c.a : ((2.0f < 1.0f) ? 12.0f : 1.0f))')
-        x = myokit.Piecewise(condx, a, condx, b, c)
-        self.assertEqual(
-            ws.ex(x),
-            '((1.2f != 0.0f) ? c.a : ((1.2f != 0.0f) ? 12.0f : 1.0f))')
+    def test_initial_value(self):
+        self.assertRaisesRegex(
+            NotImplementedError, 'Initial',
+            self.w.ex, myokit.InitialValue(self.a))
+
+    def test_prefix_plus_minus(self):
+        # Inherited from c-based
+
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0f')
+        self.eq(PrefixPlus(PrefixPlus(p)), '+(+11.0f)')
+        self.eq(PrefixPlus(PrefixPlus(PrefixPlus(p))), '+(+(+11.0f))')
+        self.eq(PrefixPlus(Number('+1')), '+1.0f')
+        self.eq(PrefixMinus(p), '-11.0f')
+        self.eq(PrefixMinus(PrefixMinus(p)), '-(-11.0f)')
+        self.eq(PrefixMinus(Number(-1)), '-(-1.0f)')
+        self.eq(PrefixMinus(PrefixMinus(Number(-2))), '-(-(-2.0f))')
+
+        # Test with operators of precedence SUM, PRODUCT
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), '+(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(PrefixPlus(Divide(b, a)), '+(b / a)')
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(PrefixMinus(Divide(b, a)), '-(b / a)')
+
+    def test_prefix_plus_minus_double(self):
+
+        w = self._target(precision=myokit.DOUBLE_PRECISION)
+        p = Number(3, 'mA')
+        self.assertEqual(w.ex(PrefixPlus(p)), '+3.0')
+        self.assertEqual(w.ex(PrefixPlus(PrefixPlus(p))), '+(+3.0)')
         self.assertEqual(
-            wd.ex(x),
-            '((1.2 != 0.0) ? c.a : ((1.2 != 0.0) ? 12.0 : 1.0))')
+            w.ex(PrefixPlus(PrefixPlus(PrefixPlus(p)))), '+(+(+3.0))')
+        self.assertEqual(w.ex(PrefixPlus(Number('+1'))), '+1.0')
+        self.assertEqual(w.ex(PrefixMinus(p)), '-3.0')
+        self.assertEqual(w.ex(PrefixMinus(PrefixMinus(p))), '-(-3.0)')
+        self.assertEqual(w.ex(PrefixMinus(Number(-1))), '-(-1.0)')
         self.assertEqual(
-            wn.ex(x),
-            '((1.2f != 0.0f) ? c.a : ((1.2f != 0.0f) ? 12.0f : 1.0f))')
+            w.ex(PrefixMinus(PrefixMinus(Number(-2)))), '-(-(-2.0))')
+
+    def test_arithmetic(self):
+        # Inherited from c-based
+
+        a, b, c = self.abc
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Multiply(a, Plus(b, c)), 'a * (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Divide(a, Minus(b, c)), 'a / (b - c)')
+        self.eq(Plus(Divide(a, b), c), 'a / b + c')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+
+    def test_quotient_remainder(self):
+        # Inherited from c-based
+
+        a, b, c = self.abc
+        self.eq(Quotient(a, Divide(b, c)), 'floor(a / (b / c))')
+        self.eq(Remainder(Plus(a, c), b), '(a + c - b * floor((a + c) / b))')
+        self.eq(Divide(c, Remainder(b, a)), 'c / ((b - a * floor(b / a)))')
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'pow(a, b)')
+        self.eq(Power(Power(a, b), c), 'pow(pow(a, b), c)')
+        self.eq(Power(a, Power(b, c)), 'pow(a, pow(b, c))')
+
+        self.eq(Power(Plus(a, b), c), 'pow(a + b, c)')
+        self.eq(Power(a, Minus(b, c)), 'pow(a, b - c)')
+        self.eq(Power(Multiply(a, b), c), 'pow(a * b, c)')
+        self.eq(Power(a, Divide(b, c)), 'pow(a, b / c)')
+
+    def test_log(self):
+        a, b = self.ab
+        self.eq(Log(a), 'native_log(a)')
+        self.eq(Log10(a), 'native_log10(a)')
+        self.eq(Log(a, b), '(native_log(a) / native_log(b))')
+
+    def test_log_no_native(self):
+        w = self._target(native_math=False)
+        a, b = self.ab
+        self.assertEqual(w.ex(Log(a)), 'log(comp.a)')
+        self.assertEqual(w.ex(Log10(b)), 'log10(comp.b)')
+        self.assertEqual(w.ex(Log(b, a)), '(log(comp.b) / log(comp.a))')
 
-    def test_fetching(self):
-        # Test fetching using ewriter method
-        w = myokit.formats.ewriter('opencl')
-        self.assertIsInstance(w, myokit.formats.opencl.OpenCLExpressionWriter)
-
-    def test_bad_operand(self):
-        # Test without a Myokit expression
-        w = myokit.formats.opencl.OpenCLExpressionWriter()
-        self.assertRaisesRegex(
-            ValueError, 'Unknown expression type', w.ex, 7)
+    def test_functions(self):
+        a = self.a
+        self.eq(Sqrt(self.a), 'native_sqrt(a)')
+        self.eq(Exp(self.a), 'native_exp(a)')
+        self.eq(Sin(self.a), 'native_sin(a)')
+        self.eq(Cos(self.a), 'native_cos(a)')
+        self.eq(Tan(self.a), 'native_tan(a)')
+        self.eq(ASin(self.a), 'asin(a)')
+        self.eq(ACos(self.a), 'acos(a)')
+        self.eq(ATan(self.a), 'atan(a)')
+        self.eq(Floor(self.a), 'floor(a)')
+        self.eq(Ceil(self.a), 'ceil(a)')
+        self.eq(Abs(self.a), 'fabs(a)')
+
+    def test_functions_no_native(self):
+        w = self._target(native_math=False)
+        self.assertEqual(w.ex(Sqrt(self.a)), 'sqrt(comp.a)')
+        self.assertEqual(w.ex(Exp(self.a)), 'exp(comp.a)')
+        self.assertEqual(w.ex(Sin(self.a)), 'sin(comp.a)')
+        self.assertEqual(w.ex(Cos(self.a)), 'cos(comp.a)')
+        self.assertEqual(w.ex(Tan(self.a)), 'tan(comp.a)')
+        self.assertEqual(w.ex(ASin(self.a)), 'asin(comp.a)')
+        self.assertEqual(w.ex(ACos(self.a)), 'acos(comp.a)')
+        self.assertEqual(w.ex(ATan(self.a)), 'atan(comp.a)')
+        self.assertEqual(w.ex(Floor(self.a)), 'floor(comp.a)')
+        self.assertEqual(w.ex(Ceil(self.a)), 'ceil(comp.a)')
+        self.assertEqual(w.ex(Abs(self.a)), 'fabs(comp.a)')
+
+    def test_conditions(self):
+        # Can be greatly simplified after
+        # https://github.com/myokit/myokit/issues/1056
+
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), '((a != 0.0f) && (b != 0.0f))')
+        self.eq(Or(d, c), '((d != 0.0f) || (c != 0.0f))')
+        self.eq(Not(And(a, b)), '(!((a != 0.0f) && (b != 0.0f)))')
+        self.eq(Not(c), '(!(c != 0.0f))')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)), '((d > c) || (b < a))')
+        self.eq(Not(Or(Number(1), Number(2))),
+                '(!((1.0f != 0.0f) || (2.0f != 0.0f)))')
+        self.eq(Not(Less(Number(1), Number(2))), '(!(1.0f < 2.0f))')
+        self.eq(Not(Plus(Number(1), Number(2))), '(!(1.0f + 2.0f != 0.0f))')
+
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0f == 0.0f) == (0.0f != 0.0f))')
+
+    def test_boolean_comparisons(self):
+        # Can be removed after https://github.com/myokit/myokit/issues/1056
+
+        a, b = self.ab
+        c1, c2 = More(Number(5), Number(3)), Less(Number(2), Number(1))
+        self.eq(Equal(c1, c2), '((5.0f > 3.0f) == (2.0f < 1.0f))')
+        self.eq(NotEqual(c1, a), '((5.0f > 3.0f) != (a != 0.0f))')
+        self.eq(More(b, c2), '((b != 0.0f) > (2.0f < 1.0f))')
+        self.eq(LessEqual(a, b), '(a <= b)')
+
+    def test_conditionals(self):
+        # Inherited from c-based
+
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), '((a == b) ? d : c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a), '((d != c) ? b : a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                '((a == b) ? c : ((a == d) ? 3.0f : 4.0f))')
+
+        # If condition is not a condition
+        self.eq(If(a, d, c), '((a != 0.0f) ? d : c)')
+        self.eq(Piecewise(a, b, c, d, Number(4)),
+                '((a != 0.0f) ? b : ((c != 0.0f) ? d : 4.0f))')
 
 
 if __name__ == '__main__':
diff --git a/myokit/tests/test_formats_python.py b/myokit/tests/test_formats_python.py
new file mode 100755
index 000000000..a2046d1b6
--- /dev/null
+++ b/myokit/tests/test_formats_python.py
@@ -0,0 +1,557 @@
+#!/usr/bin/env python3
+#
+# Tests writing of Python and NumPy equations.
+# These are used in pyfunc() and related, so numerical tests are possible and
+# desirable.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import math
+import unittest
+
+import numpy
+
+import myokit
+import myokit.formats.python
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
+
+import myokit.tests
+
+
+class PythonExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test conversion of expressions to Python.
+    This is used by pyfunc(), although that's usually done with the
+    NumPyExpressionWriter instead.
+    Numerical tests are provided.
+    """
+    _name = 'python'
+    _target = myokit.formats.python.PythonExpressionWriter
+
+    def py(self, expression, expected):
+        """ Test by converting to Python and executing. """
+        function = expression.pyfunc(use_numpy=False)
+        value = function()
+        self.assertEqual(value, expected)
+
+    def test_number(self):
+        self.eq(Number(1), '1.0')
+        self.eq(Number(-1.3274924373284374), '-1.32749243732843736e+00')
+        self.eq(Number(+1.3274924373284374), '1.32749243732843736e+00')
+        self.eq(Number(-2), '-2.0')
+        self.eq(Number(13, 'mV'), '13.0')
+        self.py(Number(14, 'mV'), 14)
+
+    def test_name(self):
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        e = myokit.PartialDerivative(self.a, self.b)
+        self.assertRaisesRegex(NotImplementedError, 'Partial', self.w.ex, e)
+
+    def test_initial_value(self):
+        e = myokit.InitialValue(self.a)
+        self.assertRaisesRegex(NotImplementedError, 'Initial', self.w.ex, e)
+
+    def test_prefix_plus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0')
+        self.eq(PrefixPlus(PrefixPlus(p)), '++11.0')
+        self.eq(PrefixPlus(Number('+1')), '+1.0')
+        self.py(PrefixPlus(Number(3)), 3)
+
+        # Test with operators of precedence SUM, PRODUCT, POWER
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), '+(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(PrefixPlus(Remainder(b, a)), '+(b % a)')
+        self.eq(Power(PrefixPlus(a), b), '(+a)**b')
+        self.eq(Power(PrefixPlus(Power(b, a)), c), '(+b**a)**c')
+        self.eq(Power(a, PrefixPlus(Power(b, c))), 'a**(+b**c)')
+
+    def test_prefix_minus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixMinus(p), '-11.0')
+        self.eq(PrefixMinus(PrefixMinus(p)), '--11.0')
+        self.eq(PrefixMinus(Number(-1)), '--1.0')
+        self.py(PrefixMinus(Number(3)), -3)
+
+        # Test with operators of precedence SUM, PRODUCT, POWER
+        a, b, c = self.abc
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(PrefixMinus(Quotient(b, a)), '-(b // a)')
+        self.eq(Power(PrefixMinus(a), b), '(-a)**b')
+        self.eq(Power(PrefixMinus(Power(c, b)), a), '(-c**b)**a')
+        self.eq(Power(a, PrefixMinus(Power(b, c))), 'a**(-b**c)')
+
+    def test_plus_minus(self):
+        a, b, c = self.abc
+        self.eq(Plus(a, b), 'a + b')
+        self.eq(Plus(Plus(a, b), c), 'a + b + c')
+        self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Minus(Minus(a, b), c), 'a - b - c')
+        self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Plus(Minus(a, b), c), 'a - b + c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+
+        self.py(Plus(Number(4), Number(2)), 6)
+        self.py(Minus(Number(5), Number(1.5)), 3.5)
+
+    def test_multiply_divide(self):
+        a, b, c = self.abc
+        self.eq(Multiply(a, b), 'a * b')
+        # Left-to-right, so (a * b) * c is the same as a * b * c...
+        self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
+        # ...but order-of-operations-wise, a * (b * c) is different!
+        self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
+        # Note that a user typing a * b * c results in (a * b) * c
+
+        self.eq(Divide(a, b), 'a / b')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+
+        self.eq(Divide(Multiply(a, b), c), 'a * b / c')
+        self.eq(Multiply(Divide(a, b), c), 'a / b * c')
+        self.eq(Divide(a, Multiply(b, c)), 'a / (b * c)')
+        self.eq(Multiply(a, Divide(b, c)), 'a * (b / c)')
+
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Multiply(a, Plus(b, c)), 'a * (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Plus(a, Multiply(b, c)), 'a + b * c')
+        self.eq(Divide(Plus(a, b), c), '(a + b) / c')
+        self.eq(Divide(a, Minus(b, c)), 'a / (b - c)')
+        self.eq(Plus(Divide(a, b), c), 'a / b + c')
+        self.eq(Minus(a, Divide(b, c)), 'a - b / c')
+
+        self.py(Multiply(Number(7), Number(9)), 63)
+        self.py(Divide(Number(5), Number(2)), 2.5)
+        self.py(Multiply(Number(7), Plus(Number(1), Number(9))), 70)
+        self.py(Divide(Minus(Number(19), Number(5)), Number(2)), 7)
+
+    def test_quotient_remainder(self):
+        a, b, c = self.abc
+
+        self.eq(Remainder(Multiply(a, b), c), 'a * b % c')
+        self.eq(Multiply(Quotient(a, b), c), 'a // b * c')
+        self.eq(Quotient(a, Multiply(b, c)), 'a // (b * c)')
+        self.eq(Multiply(a, Remainder(b, c)), 'a * (b % c)')
+
+        self.eq(Divide(a, Quotient(b, c)), 'a / (b // c)')
+        self.eq(Divide(Quotient(b, c), a), 'b // c / a')
+        self.eq(Divide(a, Remainder(b, c)), 'a / (b % c)')
+        self.eq(Divide(Remainder(b, c), a), 'b % c / a')
+
+        self.py(Quotient(Number(10), Number(4)), 2)     # 2*4 + 2
+        self.py(Remainder(Number(10), Number(4)), 2)
+        self.py(Quotient(Number(10), Number(6)), 1)     # 6 + 4
+        self.py(Remainder(Number(10), Number(6)), 4)
+        self.py(Quotient(Number(5), Number(3)), 1)      # 1*3 + 2
+        self.py(Remainder(Number(5), Number(3)), 2)
+        self.py(Quotient(Number(-5), Number(3)), -2)    # -2*3 + 1
+        self.py(Remainder(Number(-5), Number(3)), 1)
+        self.py(Quotient(Number(5), Number(-3)), -2)    # -2*-3 - 1
+        self.py(Remainder(Number(5), Number(-3)), -1)
+        self.py(Quotient(Number(-5), Number(-3)), 1)    # 1*-3 - 2
+        self.py(Remainder(Number(-5), Number(-3)), -2)
+        # The quotient has sign sign(a)*sign(b)
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'a**b')
+
+        # In Python, a**b**c = a**(b**c), whereas Myokit uses a^b^c = (a^b)^c
+        self.eq(Power(Power(a, b), c), '(a**b)**c')
+        self.eq(Power(a, Power(b, c)), 'a**b**c')
+
+        self.eq(Power(Plus(a, b), c), '(a + b)**c')
+        self.eq(Power(a, Minus(b, c)), 'a**(b - c)')
+        self.eq(Power(Multiply(a, b), c), '(a * b)**c')
+        self.eq(Power(a, Divide(b, c)), 'a**(b / c)')
+
+        self.py(Power(Number(4), Power(Number(2), Number(3))), 65536)
+        self.py(Power(Power(Number(2), Number(3)), Number(4)), 4096)
+
+    def test_functions(self):
+        a, b = self.ab
+
+        self.eq(Sqrt(a), 'math.sqrt(a)')
+        self.eq(Exp(a), 'math.exp(a)')
+        self.eq(Log(a), 'math.log(a)')
+        self.eq(Log(a, b), 'math.log(a, b)')
+        self.eq(Log10(a), 'math.log10(a)')
+        self.eq(Sin(a), 'math.sin(a)')
+        self.eq(Cos(a), 'math.cos(a)')
+        self.eq(Tan(a), 'math.tan(a)')
+        self.eq(ASin(a), 'math.asin(a)')
+        self.eq(ACos(a), 'math.acos(a)')
+        self.eq(ATan(a), 'math.atan(a)')
+        self.eq(Floor(a), 'math.floor(a)')
+        self.eq(Ceil(a), 'math.ceil(a)')
+        self.eq(Abs(a), 'abs(a)')
+
+        self.py(Sqrt(Number(9)), 3)
+        self.py(Exp(Number(3)), math.exp(3))
+        self.py(Log(Number(3)), math.log(3))
+        self.py(Log(Number(8), Number(2)), 3)
+        self.py(Log10(Number(100)), 2)
+        self.py(Sin(Number(1)), math.sin(1))
+        self.py(Cos(Number(1)), math.cos(1))
+        self.py(Tan(Number(4)), math.tan(4))
+        self.py(ASin(Number(0.4)), math.asin(0.4))
+        self.py(ACos(Number(0.4)), math.acos(0.4))
+        self.py(ATan(Number(0.4)), math.atan(0.4))
+        self.py(Floor(Number(3.9)), 3)
+        self.py(Ceil(Number(4.01)), 5)
+        self.py(Abs(PrefixMinus(Number(12))), 12)
+        self.py(Abs(Number(-13)), 13)
+
+    def test_conditions(self):
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), '(a and b)')
+        self.eq(Or(d, c), '(d or c)')
+        self.eq(Not(c), '(not c)')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) and (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)), '((d > c) or (b < a))')
+        self.eq(Not(Equal(d, d)), '(not (d == d))')
+        self.eq(Not(Or(Number(1), Number(2))), '(not (1.0 or 2.0))')
+
+        true = Equal(Number(3), Number(3))
+        self.py(true, True)
+        false = NotEqual(Number(3), Number(3))
+        self.py(false, False)
+        self.py(More(Number(5), Number(3)), True)
+        self.py(More(Number(3), Number(3)), False)
+        self.py(MoreEqual(Number(3), Number(3)), True)
+        self.py(Less(Number(3), Number(5)), True)
+        self.py(More(Number(3), Number(3)), False)
+        self.py(LessEqual(Number(3), Number(3)), True)
+
+        self.py(And(true, false), False)
+        self.py(And(true, true), True)
+        self.py(Or(true, false), True)
+        self.py(Or(true, true), True)
+        self.py(Or(false, false), False)
+        self.py(Not(true), False)
+        self.py(Not(false), True)
+
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0 == 0.0) == 0.0)')
+        self.py(Equal(Equal(Number(0), Number(0)), Number(0)), False)
+        self.py(Equal(Equal(Number(0), Number(0)), Number(1)), True)
+
+    def test_conditionals(self):
+
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), '(d if (a == b) else c)')
+        self.py(If(Equal(Number(1), Number(1)), Number(2), Number(3)), 2)
+        self.py(If(Equal(Number(10), Number(1)), Number(2), Number(3)), 3)
+
+        self.eq(Piecewise(NotEqual(d, c), b, a), '(b if (d != c) else a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                '(c if (a == b) else (3.0 if (a == d) else 4.0))')
+
+        self.py(Piecewise(Equal(Number(7), Number(7)), Number(2),
+                          Equal(Number(7), Number(8)), Number(3),
+                          Number(4)), 2)
+        self.py(Piecewise(Equal(Number(7), Number(7)), Number(2),
+                          Equal(Number(7), Number(7)), Number(3),
+                          Number(4)), 2)
+        self.py(Piecewise(NotEqual(Number(7), Number(7)), Number(2),
+                          Equal(Number(7), Number(7)), Number(3),
+                          Number(4)), 3)
+        self.py(Piecewise(NotEqual(Number(7), Number(7)), Number(2),
+                          NotEqual(Number(8), Number(8)), Number(3),
+                          Number(4)), 4)
+
+
+class NumPyExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """
+    Test conversion of expressions to NumPy-compatible Python.
+    This is used e.g. to graph expressions.
+    Numerical tests are provided.
+    """
+    _name = 'numpy'
+    _target = myokit.formats.python.NumPyExpressionWriter
+
+    def py(self, expression, expected, *arguments):
+        """ Test by executing in NumPy, possibly with vector arguments. """
+        function = expression.pyfunc(use_numpy=True)
+        if arguments:
+            arguments = [numpy.array(arg) for arg in arguments]
+            values = function(*arguments)
+            self.assertEqual(list(values), list(expected))
+        else:
+            value = function()
+            self.assertEqual(value, expected)
+
+    def test_number(self):
+        self.eq(Number(2), '2.0')
+        self.eq(Number(-1), '-1.0')
+        self.eq(Number(14, 'kV'), '14.0')
+        self.py(Number(-7, 'mA'), -7)
+
+    def test_name(self):
+        self.eq(self.b, 'b')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.d), 'COMP.D')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.d), 'dot(d)')
+
+    def test_partial_derivative(self):
+        e = myokit.InitialValue(self.a)
+        self.assertRaisesRegex(NotImplementedError, 'Initial', self.w.ex, e)
+
+    def test_initial_value(self):
+        e = myokit.PartialDerivative(self.a, self.b)
+        self.assertRaisesRegex(NotImplementedError, 'Partial', self.w.ex, e)
+
+    def test_prefix_plus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0')
+        self.eq(PrefixPlus(PrefixPlus(p)), '++11.0')
+        self.eq(PrefixPlus(Number('+1')), '+1.0')
+        self.py(PrefixPlus(Number(3)), 3)
+        self.py(PrefixPlus(self.a), [3, 4], [3, 4])
+
+        # Test with operators of precedence SUM, PRODUCT, POWER
+        a, b, c = self.abc
+        self.eq(PrefixPlus(Plus(a, b)), '+(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(PrefixPlus(Remainder(b, a)), '+(b % a)')
+        self.eq(Power(PrefixPlus(Power(b, b)), c), '(+b**b)**c')
+        self.eq(Power(a, PrefixPlus(Power(b, c))), 'a**(+b**c)')
+
+    def test_prefix_minus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixMinus(p), '-11.0')
+        self.eq(PrefixMinus(PrefixMinus(p)), '--11.0')
+        self.eq(PrefixMinus(Number(-1)), '--1.0')
+        self.py(PrefixMinus(Number(3)), -3)
+        self.py(PrefixMinus(self.a), [3, -4], [-3, 4])
+
+        # Test with operators of precedence SUM, PRODUCT, POWER
+        a, b, c = self.abc
+        self.eq(PrefixMinus(Minus(a, b)), '-(a - b)')
+        self.eq(Multiply(PrefixMinus(Plus(b, a)), c), '-(b + a) * c')
+        self.eq(PrefixMinus(Quotient(b, a)), '-(b // a)')
+        self.eq(Power(PrefixMinus(Power(c, b)), a), '(-c**b)**a')
+        self.eq(Power(a, PrefixMinus(Power(b, c))), 'a**(-b**c)')
+
+    def test_plus_minus(self):
+        a, b, c = self.abc
+        self.eq(Plus(a, b), 'a + b')
+        self.eq(Plus(Plus(a, b), c), 'a + b + c')
+        self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Minus(Minus(a, b), c), 'a - b - c')
+        self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
+
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Plus(Minus(a, b), c), 'a - b + c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+
+        self.py(Plus(Number(4), Number(2)), 6)
+        self.py(Minus(Number(5), Number(1.5)), 3.5)
+        self.py(Plus(a, Number(2)), [5, 1], [3, -1])
+        self.py(Minus(Number(5), b), [2, -5], [3, 10])
+        self.py(Plus(a, b), [6, 5, 4], [1, 2, 3], [5, 3, 1])
+        self.py(Minus(a, b), [1, 2, 3], [8, 2, 5], [7, 0, 2])
+
+    def test_multiply_divide(self):
+        a, b, c = self.abc
+        self.eq(Multiply(a, b), 'a * b')
+        # Left-to-right, so (a * b) * c is the same as a * b * c...
+        self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
+        # ...but order-of-operations-wise, a * (b * c) is different!
+        self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
+        # Note that a user typing a * b * c results in (a * b) * c
+
+        self.eq(Divide(a, b), 'a / b')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+
+        self.eq(Divide(Multiply(a, b), c), 'a * b / c')
+        self.eq(Multiply(Divide(a, b), c), 'a / b * c')
+        self.eq(Divide(a, Multiply(b, c)), 'a / (b * c)')
+        self.eq(Multiply(a, Divide(b, c)), 'a * (b / c)')
+
+        self.eq(Remainder(Multiply(a, b), c), 'a * b % c')
+        self.eq(Multiply(Quotient(a, b), c), 'a // b * c')
+        self.eq(Quotient(a, Multiply(b, c)), 'a // (b * c)')
+        self.eq(Multiply(a, Remainder(b, c)), 'a * (b % c)')
+
+        self.eq(Divide(a, Quotient(b, c)), 'a / (b // c)')
+        self.eq(Divide(Quotient(b, c), a), 'b // c / a')
+        self.eq(Divide(a, Remainder(b, c)), 'a / (b % c)')
+        self.eq(Divide(Remainder(b, c), a), 'b % c / a')
+
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Multiply(a, Plus(b, c)), 'a * (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Plus(a, Multiply(b, c)), 'a + b * c')
+        self.eq(Divide(Plus(a, b), c), '(a + b) / c')
+        self.eq(Divide(a, Minus(b, c)), 'a / (b - c)')
+        self.eq(Plus(Divide(a, b), c), 'a / b + c')
+        self.eq(Minus(a, Divide(b, c)), 'a - b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+
+        self.py(Multiply(Number(7), Number(9)), 63)
+        self.py(Divide(Number(5), Number(2)), 2.5)
+        self.py(Multiply(a, Number(2)), [6, -2], [3, -1])
+        self.py(Divide(a, b), [5, -3, 10], 60, [12, -20, 6])
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'a**b')
+
+        # In Python, a**b**c = a**(b**c), whereas Myokit uses a^b^c = (a^b)^c
+        self.eq(Power(Power(a, b), c), '(a**b)**c')
+        self.eq(Power(a, Power(b, c)), 'a**b**c')
+
+        self.eq(Power(Plus(a, b), c), '(a + b)**c')
+        self.eq(Power(a, Minus(b, c)), 'a**(b - c)')
+        self.eq(Power(Multiply(a, b), c), '(a * b)**c')
+        self.eq(Power(a, Divide(b, c)), 'a**(b / c)')
+
+        self.py(Power(Number(4), Power(Number(2), Number(3))), 65536)
+        self.py(Power(Power(Number(2), Number(3)), Number(4)), 4096)
+        self.py(Power(a, Power(b, c)),
+                [65536, 1, 4], [4, 1, 2], [2, 1, 4], [3, 1, 0.5])
+        self.py(Power(Power(a, b), c),
+                [4096, 0.01], [2, 10.0], [3, 2], [4, -1])
+
+    def test_functions(self):
+        a, b, c = self.abc
+
+        self.eq(Sqrt(a), 'numpy.sqrt(a)')
+        self.eq(Exp(a), 'numpy.exp(a)')
+        self.eq(Log(a), 'numpy.log(a)')
+        # Log replaced by divide always adds brackets, because parent element
+        # won't have added any (never needed around a log(a, b) function).
+        self.eq(Log(a, b), '(numpy.log(a) / numpy.log(b))')
+        self.eq(Divide(a, Log(b, c)), 'a / (numpy.log(b) / numpy.log(c))')
+        self.eq(Log10(a), 'numpy.log10(a)')
+        self.eq(Sin(a), 'numpy.sin(a)')
+        self.eq(Cos(a), 'numpy.cos(a)')
+        self.eq(Tan(a), 'numpy.tan(a)')
+        self.eq(ASin(a), 'numpy.arcsin(a)')
+        self.eq(ACos(a), 'numpy.arccos(a)')
+        self.eq(ATan(a), 'numpy.arctan(a)')
+        self.eq(Floor(a), 'numpy.floor(a)')
+        self.eq(Ceil(a), 'numpy.ceil(a)')
+        self.eq(Abs(a), 'numpy.abs(a)')
+
+        self.py(Sqrt(a), [3, 4], [9, 16])
+        self.py(Exp(a), numpy.exp([2, 3]), [2, 3])
+        self.py(Log(a), numpy.log([3, 5]), [3, 5])
+        self.py(Log(a, b), [3, 4], [27, 16], [3, 2])
+        self.py(Log10(a), [2, 3], [100, 1000])
+        self.py(Sin(a), numpy.sin([1, 2]), [1, 2])
+        self.py(Cos(a), numpy.cos([3, 4]), [3, 4])
+        self.py(Tan(a), numpy.tan([5, 6]), [5, 6])
+        self.py(ASin(a), numpy.arcsin([0.1, 0.2]), [0.1, 0.2])
+        self.py(ACos(a), numpy.arccos([0.3, 0.4, 0.5]), [0.3, 0.4, 0.5])
+        self.py(ATan(a), numpy.arctan([0.1]), [0.1])
+        self.py(Floor(a), [3, 3], [3.1, 3.9])
+        self.py(Ceil(a), [2, 3], [1.1, 3])
+        self.py(Abs(a), [13, 13], [13, -13])
+        self.py(Abs(PrefixMinus(a)), [1, 2], [1, -2])
+
+    def test_conditions(self):
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), 'numpy.logical_and(a, b)')
+        self.eq(Or(d, c), 'numpy.logical_or(d, c)')
+        self.eq(Not(c), 'numpy.logical_not(c)')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)),
+                'numpy.logical_and((a == b), (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)),
+                'numpy.logical_or((d > c), (b < a))')
+        self.eq(Not(Equal(d, d)), 'numpy.logical_not((d == d))')
+        self.eq(Not(Or(Number(1), Number(2))),
+                'numpy.logical_not(numpy.logical_or(1.0, 2.0))')
+
+        true = Equal(Number(3), Number(3))
+        self.py(true, True)
+        false = NotEqual(Number(3), Number(3))
+        self.py(false, False)
+        self.py(More(Number(5), Number(3)), True)
+        self.py(More(Number(3), Number(3)), False)
+        self.py(MoreEqual(Number(3), Number(3)), True)
+        self.py(Less(Number(3), Number(5)), True)
+        self.py(More(Number(3), Number(3)), False)
+        self.py(LessEqual(Number(3), Number(3)), True)
+        self.py(And(true, false), False)
+        self.py(And(true, true), True)
+        self.py(Or(a, b), [True, True, True, False],
+                [True, True, False, False], [True, False, True, False])
+        self.py(Not(a), [True, False], [False, True])
+
+    def test_conditionals(self):
+        a, b, c, d = self.abcd
+        e, f, g = self.efg
+
+        self.eq(If(Equal(a, b), d, c), 'numpy.select([(a == b)], [d], c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a),
+                'numpy.select([(d != c)], [b], a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                'numpy.select([(a == b), (a == d)], [c, 3.0], 4.0)')
+
+        self.py(If(Equal(Number(10), Number(1)), Number(2), Number(3)), 3)
+        self.py(If(Equal(a, b), c, d), [3, 2], [2, 2], [2, 3], [3, 4], [1, 2])
+        self.py(Piecewise(Equal(a, b), c, Equal(d, e), f, g),
+                [1, 2, 30], [1, 1, 1], [1, 2, 3], [1, 1, 1], [1, 4, 5],
+                [1, 4, 6], [2, 2, 2], [3, 3, 30])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_stan.py b/myokit/tests/test_formats_stan.py
new file mode 100755
index 000000000..89e50aeeb
--- /dev/null
+++ b/myokit/tests/test_formats_stan.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+#
+# Tests writing of Stan equations.
+#
+# This file is part of Myokit.
+# See http://myokit.org for copyright, sharing, and licensing details.
+#
+import unittest
+
+import myokit
+import myokit.formats.stan
+
+from myokit import (
+    Number, PrefixPlus, PrefixMinus, Plus, Minus,
+    Multiply, Divide, Quotient, Remainder, Power, Sqrt,
+    Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
+    Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
+    If, Piecewise,
+)
+
+import myokit.tests
+
+
+class StanExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
+    """ Test conversion of expressions to Stan. """
+    _name = 'stan'
+    _target = myokit.formats.stan.StanExpressionWriter
+
+    def test_number(self):
+        self.eq(Number(1), '1.0')
+        self.eq(Number(-1.3274924373284374), '-1.32749243732843736e+00')
+        self.eq(Number(+1.3274924373284374), '1.32749243732843736e+00')
+        self.eq(Number(-2), '-2.0')
+        self.eq(Number(13, 'mV'), '13.0')
+
+    def test_name(self):
+        self.eq(self.a, 'a')
+        w = self._target()
+        w.set_lhs_function(lambda v: v.var().qname().upper())
+        self.assertEqual(w.ex(self.a), 'COMP.A')
+
+    def test_derivative(self):
+        self.eq(myokit.Derivative(self.a), 'dot(a)')
+
+    def test_partial_derivative(self):
+        e = myokit.PartialDerivative(self.a, self.b)
+        self.assertRaisesRegex(NotImplementedError, 'Partial', self.w.ex, e)
+
+    def test_initial_value(self):
+        e = myokit.InitialValue(self.a)
+        self.assertRaisesRegex(NotImplementedError, 'Initial', self.w.ex, e)
+
+    def test_prefix_plus_minus(self):
+        # Test with numbers
+        p = Number(11, 'kV')
+        self.eq(PrefixPlus(p), '+11.0')
+        self.eq(PrefixPlus(PrefixPlus(p)), '++11.0')
+        self.eq(PrefixMinus(p), '-11.0')
+        self.eq(PrefixMinus(PrefixMinus(p)), '--11.0')
+        self.eq(PrefixMinus(Number(-1)), '--1.0')
+
+        a, b, c = self.abc
+        self.eq(PrefixMinus(Plus(a, b)), '-(a + b)')
+        self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
+        self.eq(Power(PrefixMinus(a), b), '(-a)^b')
+        self.eq(Power(PrefixPlus(Power(b, a)), c), '(+b^a)^c')
+        self.eq(Power(a, PrefixMinus(Power(b, c))), 'a^(-b^c)')
+
+    def test_plus_minus(self):
+        a, b, c = self.abc
+        self.eq(Plus(a, b), 'a + b')
+        self.eq(Plus(Plus(a, b), c), 'a + b + c')
+        self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Minus(Minus(a, b), c), 'a - b - c')
+        self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
+        self.eq(Minus(a, b), 'a - b')
+        self.eq(Plus(Minus(a, b), c), 'a - b + c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+        self.eq(Minus(Plus(a, b), c), 'a + b - c')
+        self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
+
+    def test_multiply_divide(self):
+        a, b, c = self.abc
+        self.eq(Multiply(a, b), 'a * b')
+        self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
+        self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
+        self.eq(Divide(a, b), 'a / b')
+        self.eq(Divide(Divide(a, b), c), 'a / b / c')
+        self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
+
+        self.eq(Divide(Multiply(a, b), c), 'a * b / c')
+        self.eq(Multiply(Divide(a, b), c), 'a / b * c')
+        self.eq(Divide(a, Multiply(b, c)), 'a / (b * c)')
+        self.eq(Multiply(a, Divide(b, c)), 'a * (b / c)')
+
+        self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
+        self.eq(Divide(a, Plus(b, c)), 'a / (b + c)')
+        self.eq(Minus(Multiply(a, b), c), 'a * b - c')
+        self.eq(Plus(a, Divide(b, c)), 'a + b / c')
+
+    def test_quotient_remainder(self):
+        a, b, c = self.abc
+
+        self.eq(Quotient(c, a), 'floor(c / a)')
+        self.eq(Remainder(c, a), '(c - a * floor(c / a))')
+
+    def test_power(self):
+        a, b, c = self.abc
+        self.eq(Power(a, b), 'a^b')
+        self.eq(Power(Plus(a, b), c), '(a + b)^c')
+        self.eq(Power(a, Minus(b, c)), 'a^(b - c)')
+        self.eq(Power(Multiply(a, b), c), '(a * b)^c')
+        self.eq(Power(a, Divide(b, c)), 'a^(b / c)')
+
+        # Stan has a right-associative power operator, so must add brackets
+        # to get Myokit behavuour
+        self.eq(Power(Power(a, b), c), '(a^b)^c')
+        self.eq(Power(a, Power(b, c)), 'a^b^c')
+
+    def test_functions(self):
+        a, b = self.ab
+
+        self.eq(Sqrt(a), 'sqrt(a)')
+        self.eq(Exp(a), 'exp(a)')
+        self.eq(Log(a), 'log(a)')
+        self.eq(Log(a, b), '(log(a) / log(b))')
+        self.eq(Log10(a), 'log10(a)')
+        self.eq(Sin(a), 'sin(a)')
+        self.eq(Cos(a), 'cos(a)')
+        self.eq(Tan(a), 'tan(a)')
+        self.eq(ASin(a), 'asin(a)')
+        self.eq(ACos(a), 'acos(a)')
+        self.eq(ATan(a), 'atan(a)')
+        self.eq(Floor(a), 'floor(a)')
+        self.eq(Ceil(a), 'ceil(a)')
+        self.eq(Abs(a), 'abs(a)')
+
+    def test_conditions(self):
+        a, b, c, d = self.abcd
+
+        self.eq(And(a, b), '(a && b)')
+        self.eq(Or(d, c), '(d || c)')
+        self.eq(Not(c), '(!c)')
+
+        self.eq(Equal(a, b), '(a == b)')
+        self.eq(NotEqual(a, b), '(a != b)')
+        self.eq(More(b, a), '(b > a)')
+        self.eq(Less(d, c), '(d < c)')
+        self.eq(MoreEqual(c, a), '(c >= a)')
+        self.eq(LessEqual(b, d), '(b <= d)')
+
+        self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
+        self.eq(Or(More(d, c), Less(b, a)), '((d > c) || (b < a))')
+        self.eq(Not(Equal(d, d)), '(!(d == d))')
+        self.eq(Not(Or(Number(1), Number(2))), '(!(1.0 || 2.0))')
+
+        self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
+                '((0.0 == 0.0) == 0.0)')
+
+    def test_conditionals(self):
+        a, b, c, d = self.abcd
+        self.eq(If(Equal(a, b), d, c), '((a == b) ? d : c)')
+        self.eq(Piecewise(NotEqual(d, c), b, a), '((d != c) ? b : a)')
+        self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
+                '((a == b) ? c : ((a == d) ? 3.0 : 4.0))')
+
+        # Extra parentheses if condition is not a condition
+        self.eq(If(a, d, c), '(a ? d : c)')
+        self.eq(Piecewise(a, b, c, d, Number(4)),
+                '(a ? b : (c ? d : 4.0))')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/myokit/tests/test_formats_sympy.py b/myokit/tests/test_formats_sympy.py
index 900c6af3b..cae6531cd 100755
--- a/myokit/tests/test_formats_sympy.py
+++ b/myokit/tests/test_formats_sympy.py
@@ -64,6 +64,9 @@ def test_reader_writer(self):
         self.assertEqual(w.ex(x), cb)
         # Note: Sympy doesn't seem to have a prefix plus
         self.assertEqual(r.ex(cb), b)
+        x = myokit.Divide(myokit.PrefixPlus(myokit.Plus(a, b)), self._b)
+        y = r.ex(w.ex(x))
+        self.assertEqual(y.code(), 'c.b^-1 * (12 + c.a)')
 
         # Prefix minus
         # Note: SymPy treats -x as Mul(NegativeOne, x)
@@ -105,8 +108,12 @@ def test_reader_writer(self):
 
         # Power
         x = myokit.Power(a, b)
-        self.assertEqual(w.ex(x), ca ** cb)
-        self.assertEqual(float(r.ex(ca ** cb)), float(x))
+        self.assertEqual(w.ex(x), ca**cb)
+        self.assertEqual(float(r.ex(ca**cb)), float(x))
+        x = myokit.Power(myokit.Power(a, b), self._b)
+        self.assertEqual(w.ex(x), (ca**cb)**sp.Symbol('c.b'))
+        x = myokit.Power(a, myokit.Power(b, self._b))
+        self.assertEqual(w.ex(x), ca**(cb**sp.Symbol('c.b')))
 
         # Sqrt
         x = myokit.Sqrt(a)
diff --git a/myokit/tests/test_lib_plots.py b/myokit/tests/test_lib_plots.py
index b46a52a09..decacd58d 100755
--- a/myokit/tests/test_lib_plots.py
+++ b/myokit/tests/test_lib_plots.py
@@ -41,14 +41,12 @@ def test_simulation_times(self):
         # Stair
         fig = plt.figure()
         plots.simulation_times(st, rt, ev, 'stair')
-        plt.show()
         plt.close(fig)
         self.assertRaises(ValueError, plots.simulation_times, mode='stair')
 
         # Inverse stair
         fig = plt.figure()
         plots.simulation_times(st, rt, ev, 'stair_inverse')
-        plt.show()
         plt.close(fig)
         self.assertRaises(
             ValueError, plots.simulation_times, mode='stair_inverse')
@@ -56,21 +54,18 @@ def test_simulation_times(self):
         # Load
         fig = plt.figure()
         plots.simulation_times(st, rt, ev, 'load')
-        plt.show()
         plt.close(fig)
         self.assertRaises(ValueError, plots.simulation_times, mode='load')
 
         # Histogram
         fig = plt.figure()
         plots.simulation_times(st, rt, ev, 'histo')
-        plt.show()
         plt.close(fig)
         self.assertRaises(ValueError, plots.simulation_times, mode='histo')
 
         # Time per step
         fig = plt.figure()
         plots.simulation_times(st, rt, ev, 'time_per_step')
-        plt.show()
         plt.close(fig)
         self.assertRaises(
             ValueError, plots.simulation_times, mode='time_per_step')
@@ -78,7 +73,6 @@ def test_simulation_times(self):
         # Evaluations per step
         fig = plt.figure()
         plots.simulation_times(st, rt, ev, 'eval_per_step')
-        plt.show()
         plt.close(fig)
         self.assertRaises(
             ValueError, plots.simulation_times, mode='eval_per_step')
@@ -111,7 +105,6 @@ def test_current_arrows(self):
         fig = plt.figure()
         plots.current_arrows(d, 'membrane.V', currents)
         plt.close(fig)
-        plt.show()
 
         # Massive peak at final point
         d = d.npview()
@@ -119,7 +112,6 @@ def test_current_arrows(self):
         fig = plt.figure()
         plots.current_arrows(d, 'membrane.V', currents)
         plt.close(fig)
-        plt.show()
 
     def test_cumulative_current(self):
         # Test the cumulative current plot.
@@ -140,7 +132,6 @@ def test_cumulative_current(self):
         plots.cumulative_current(d, currents)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         # Labels set
         labels = ['I_Ca', 'I_K', 'I_K1', 'I_Kp', 'I_b']
@@ -148,7 +139,6 @@ def test_cumulative_current(self):
         plots.cumulative_current(d, currents, labels=labels)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         # Colors set
         colors = ['green', 'blue', 'yellow', 'brown', 'gray']
@@ -156,7 +146,6 @@ def test_cumulative_current(self):
         plots.cumulative_current(d, currents, colors=colors)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         # Not enough colors set (will repeat array)
         colors = ['green', 'blue']
@@ -164,35 +153,30 @@ def test_cumulative_current(self):
         plots.cumulative_current(d, currents, colors=colors)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         # Integrate currents to charges
         fig = plt.figure()
         plots.cumulative_current(d, currents, integrate=True)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         # Normalize currents
         fig = plt.figure()
         plots.cumulative_current(d, currents, normalize=True)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         # Normalize currents and set maximum number of currents shown
         fig = plt.figure()
         plots.cumulative_current(d, currents, normalize=True, max_currents=3)
         plt.legend()
         plt.close(fig)
-        plt.show()
 
         with WarningCollector() as w:
             fig = plt.figure()
             plots.cumulative_current(d, currents, normalise=True)
             plt.legend()
             plt.close(fig)
-            plt.show()
         self.assertIn('deprecated', w.text())