Repository: myokit/myokit (language: python)

Issue:
Expression validation: Check for appropriate Condition/not-a-Condition types?
Expression._validate already checks that all operands are Expression

Could extend this to check that conditions aren't used where numbers are expected, and that numbers aren't used in conditions (no more `if(1, ...` or `1 + and(2, 3 == 3)`


Discussion / hints:
See also #604 
> Expression._validate already checks that all operands are Expression
> 
> Could extend this to check that conditions aren't used where numbers are expected, and that numbers aren't used in conditions (no more `if(1, ...` or `1 + and(2, 3 == 3)`

Not sure why validate does this, instead of the constructor.
Even when using the expression system for non-myokit stuff, it's still `Name` objects that keep the links to non-myokit objects...

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b21e9fb05..4d02602d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,12 +4,16 @@ This page lists the main changes made to Myokit in each release.
 
 ## Unreleased
 - Added
+  - [#1075](https://github.com/myokit/myokit/pull/1075) Add a `myokit.TypeError` exception, which inherits from `myokit.IntegrityError` but is used specifically if one type of expression was expected but another was found.
 - Changed
+  - [#1075](https://github.com/myokit/myokit/pull/1075) Expressions will now raise a `myokit.TypeError` in the constructor if their operands are unexpected expression types, e.g. if numbers and booleans are mixed.
+  - [#1074](https://github.com/myokit/myokit/pull/1074) Expressions will now raise a `myokit.IntegrityError` in the constructor if their operands are not expression objects: previously, this check was only performed by `Expression.validate()`.
   - [#1067](https://github.com/myokit/myokit/pull/1067) Improved the meta data loaded from HEKA files when creating a `myokit.DataLog`.
-- Deprecated
 - Removed
+  - [#1075](https://github.com/myokit/myokit/pull/1075) Removed the `PrefixCondition` base class previously used in the expression system, as only `Not` inherited from it.
 - Fixed
   - [#1071](https://github.com/myokit/myokit/pull/1071) When DataLog.split_periodic does not make a split, a length 1 list is returned instead of a `DataLog`.
+  - [#1075](https://github.com/myokit/myokit/pull/1075) Added missing `Expression` base classes to public API.
 
 ## [1.36.1] - 2024-05-10
 - Added
diff --git a/docs/source/api_core/exceptions.rst b/docs/source/api_core/exceptions.rst
index c76f47ce0..1b5380957 100644
--- a/docs/source/api_core/exceptions.rst
+++ b/docs/source/api_core/exceptions.rst
@@ -83,6 +83,8 @@ Inheriting classes
 
 .. autoclass:: SimultaneousProtocolEventError
 
+.. autoclass:: TypeError
+
 .. autoclass:: UnresolvedReferenceError
 
 .. autoclass:: UnusedVariableError
diff --git a/docs/source/api_core/expressions.rst b/docs/source/api_core/expressions.rst
index 2a1369356..cb39c7454 100644
--- a/docs/source/api_core/expressions.rst
+++ b/docs/source/api_core/expressions.rst
@@ -42,33 +42,6 @@ left-hand side equations must extend the class :class:`LhsExpression`.
 .. _api/myokit.Derivative:
 .. autoclass:: Derivative
 
-Operators
----------
-
-.. _api/myokit.PrefixExpression:
-.. autoclass:: PrefixExpression
-
-.. _api/myokit.InfixExpression:
-.. autoclass:: InfixExpression
-
-Functions
----------
-
-.. _api/myokit.Function:
-.. autoclass:: Function
-
-Conditions
-----------
-
-.. _api/myokit.Condition:
-.. autoclass:: Condition
-
-.. _api/myokit.PrefixCondition:
-.. autoclass:: PrefixCondition
-
-.. _api/myokit.InfixCondition:
-.. autoclass:: InfixCondition
-
 Unary plus and minus
 --------------------
 
@@ -93,6 +66,12 @@ Addition and multiplication
 .. _api/myokit.Divide:
 .. autoclass:: Divide
 
+Functions
+---------
+
+.. _api/myokit.Function:
+.. autoclass:: Function
+
 Powers and roots
 ----------------
 
@@ -143,6 +122,10 @@ All trigonometric functions use angles in radians.
 
 Conditional operators
 ---------------------
+
+.. _api/myokit.Condition:
+.. autoclass:: Condition
+
 .. _api/myokit.If:
 .. autoclass:: If
 
@@ -208,3 +191,28 @@ User-defined functions
 .. _api/myokit.UserFunction:
 .. autoclass:: UserFunction
 
+Subtypes
+--------
+.. _api/myokit.PrefixExpression:
+.. autoclass:: PrefixExpression
+
+.. _api/myokit.NumericalPrefixExpression:
+.. autoclass:: NumericalPrefixExpression
+
+.. _api/myokit.InfixExpression:
+.. autoclass:: InfixExpression
+
+.. _api/myokit.NumericalInfixExpression:
+.. autoclass:: NumericalInfixExpression
+
+.. _api/myokit.UnaryNumericalFunction:
+.. autoclass:: UnaryNumericalFunction
+
+.. _api/myokit.UnaryNumericalDimensionlessFunction:
+.. autoclass:: UnaryNumericalDimensionlessFunction
+
+.. _api/myokit.InfixCondition:
+.. autoclass:: InfixCondition
+
+.. _api/myokit.BinaryComparison:
+.. autoclass:: BinaryComparison
diff --git a/docs/source/api_index/myokit.rst b/docs/source/api_index/myokit.rst
index 43bea704a..1125287cb 100644
--- a/docs/source/api_index/myokit.rst
+++ b/docs/source/api_index/myokit.rst
@@ -9,6 +9,7 @@ myokit
 - :class:`myokit.ASin`
 - :class:`myokit.ATan`
 - :class:`myokit.Benchmarker`
+- :class:`myokit.BinaryComparison`
 - :class:`myokit.Ceil`
 - :meth:`myokit.check_name`
 - :class:`myokit.CModel`
@@ -96,6 +97,8 @@ myokit
 - :class:`myokit.NotEqual`
 - :class:`myokit.Number`
 - :class:`myokit.NumericalError`
+- :class:`myokit.NumericalInfixExpression`
+- :class:`myokit.NumericalPrefixExpression`
 - :meth:`myokit.numpy_writer`
 - :class:`myokit.ObjectWithMetaData`
 - :class:`myokit.OpenCL`
@@ -116,7 +119,6 @@ myokit
 - :class:`myokit.Piecewise`
 - :class:`myokit.Plus`
 - :class:`myokit.Power`
-- :class:`myokit.PrefixCondition`
 - :class:`myokit.PrefixExpression`
 - :class:`myokit.PrefixMinus`
 - :class:`myokit.PrefixPlus`
@@ -159,7 +161,10 @@ myokit
 - :meth:`myokit.time`
 - :class:`myokit.Timeout`
 - :class:`myokit.TimeSeriesProtocol`
+- :class:`myokit.TypeError`
 - :class:`myokit.Unit`
+- :class:`myokit.UnaryNumericalFunction`
+- :class:`myokit.UnaryNumericalDimensionlessFunction`
 - :class:`myokit.UnresolvedReferenceError`
 - :class:`myokit.UnusedVariableError`
 - :class:`myokit.UserFunction`
diff --git a/docs/source/syntax/model.rst b/docs/source/syntax/model.rst
index 1d4546221..22fa47d8b 100644
--- a/docs/source/syntax/model.rst
+++ b/docs/source/syntax/model.rst
@@ -238,6 +238,14 @@ Some examples of valid unit declarations are::
     v_cyt = volume * 0.678
         in [uL]
 
+Types
+=====
+All expressions in Myokit evaluate to either a (floating point) number or a
+condition. Conditions can only appear inside ``if`` or ``piecewise`` statements
+(see below), or as operands to the ``and``, ``or``, or ``not`` operators.
+The value, initial value, and derivative of a variable can never be a
+condition.
+
 Foreign variables
 =================
 Variables from other components can be addressed using the syntax
@@ -365,6 +373,8 @@ In addition, + and - can be used to indicate signs: ``+5+-2=3``
 
 Parts of expressions can be grouped using parentheses ``5 * (4 - 2) = 10``
 
+These expressions all require numbers as input, and return a number.
+
 The following conditional operators are defined:
 
 +--------+-----------------------+
@@ -381,8 +391,12 @@ The following conditional operators are defined:
 | ``<=`` | Less than or equal    |
 +--------+-----------------------+
 
+These conditions all require numbers as input, but return a condition.
+
 Conditions can be strung together using ``and`` and ``or``, or negated with
 ``not``.
+These three operators require a condition as input and return another
+condition.
 
 Pre-defined Functions
 =====================
@@ -421,6 +435,8 @@ The following functions are defined:
 In addition, the expression ``dot(x)`` can be used to reference the time
 derivative of state variable ``x``.
 
+These functions all require a number as input.
+
 Conditional statements (if)
 ===========================
 Simple conditional statements can be made using the ``if`` function::
@@ -436,6 +452,8 @@ Which should be read as::
     else
         x = 0.5 * exp((V + 19) / 1.2)
 
+The ``if`` function expects a condition as first argument, and numbers as the
+second and third argument.
 
 Piecewise conditional statements
 ================================
@@ -459,6 +477,10 @@ Which should be read as::
 The final "else" part is not optional. If conditions overlap, only the first
 condition that evaluates to true will be used.
 
+The arguments to a ``piecewise`` condition should be a condition followed by a
+number, followed by any number of condition-number pairs, finishing with a
+number.
+
 .. _syntax/template_functions:
 
 User defined functions
diff --git a/myokit/__init__.py b/myokit/__init__.py
index 6da6a90b9..789b9fa1a 100644
--- a/myokit/__init__.py
+++ b/myokit/__init__.py
@@ -336,29 +336,12 @@
     SimulationCancelledError,
     SimulationError,
     SimultaneousProtocolEventError,
+    TypeError,
     UnresolvedReferenceError,
     UnusedVariableError,
     VariableMappingError,
 )
 
-# Check if all errors imported
-# Dynamically importing them doesn't seem to be possible, and forgetting to
-#  import an error creates a hard to debug bug (something needs to go wrong
-#  before the interpreter reaches the code raising the error and notices it's
-#  not there).
-if not __release__:  # pragma: no cover
-    from . import _err  # noqa
-    import inspect  # noqa
-    _globals = globals()
-    ex, name, clas = None, None, None
-    for ex in inspect.getmembers(_err):
-        name, clas = ex
-        if type(clas) == type(MyokitError) and issubclass(clas, MyokitError):
-            if name not in _globals:
-                raise Exception('Failed to import exception: ' + name)
-    del ex, name, clas, _globals, inspect  # Prevent public visibility
-    del _err
-
 # Tools
 from . import float  # noqa
 from . import tools  # noqa
@@ -386,6 +369,7 @@
     And,
     ASin,
     ATan,
+    BinaryComparison,
     Ceil,
     Condition,
     Cos,
@@ -411,6 +395,8 @@
     Multiply,
     Name,
     Number,
+    NumericalInfixExpression,
+    NumericalPrefixExpression,
     Not,
     NotEqual,
     Or,
@@ -418,7 +404,6 @@
     Piecewise,
     Plus,
     Power,
-    PrefixCondition,
     PrefixExpression,
     PrefixMinus,
     PrefixPlus,
@@ -427,6 +412,8 @@
     Sin,
     Sqrt,
     Tan,
+    UnaryNumericalFunction,
+    UnaryNumericalDimensionlessFunction,
 )
 
 # Unit and quantity
diff --git a/myokit/_err.py b/myokit/_err.py
index ade600bd8..f1ed9fa7f 100644
--- a/myokit/_err.py
+++ b/myokit/_err.py
@@ -402,6 +402,20 @@ class SimultaneousProtocolEventError(MyokitError):
     """
 
 
+class TypeError(IntegrityError):
+    """
+    Raised by the expression system if expressions of one type are required but
+    others are found.
+
+    For example, when a Derivative is created with an argument that is not a
+    Name, when a condition is given as input to a numerical operator (e.g.
+    ``log(1 == 2)``), or when a conditional operator is applied to a number
+    (e.g. ``and(1, 2)``).
+
+    *Extends:* :class:`myokit.IntegrityError`
+    """
+
+
 class UnresolvedReferenceError(IntegrityError):
     """
     Raised when a reference to a variable cannot be resolved.
diff --git a/myokit/_expressions.py b/myokit/_expressions.py
index 6c946e529..b5cd1fa3c 100644
--- a/myokit/_expressions.py
+++ b/myokit/_expressions.py
@@ -11,8 +11,6 @@
 
 import myokit
 
-from myokit import IntegrityError
-
 
 # Expression precedence levels
 FUNCTION_CALL = 70
@@ -50,7 +48,7 @@ def __init__(self, operands=None):
         self._operands = () if operands is None else operands
         for op in self._operands:
             if not isinstance(op, Expression):
-                raise IntegrityError(
+                raise myokit.IntegrityError(
                     'Expression operands must be other Expression objects.'
                     f' Found: {type(op)}.', self._token)
 
@@ -750,7 +748,8 @@ def _validate(self, trail):
 
         # Check for cyclical dependency
         if id(self) in trail:
-            raise IntegrityError('Cyclical expression found', self._token)
+            raise myokit.IntegrityError(
+                'Cyclical expression found', self._token)
         trail2 = trail + [id(self)]
         # It's okay to do this check with id's. Even if there are multiple
         # objects that are equal, if they're cyclical you'll get back round to
@@ -765,10 +764,11 @@ def _validate(self, trail):
         # Cache validation status
         self._cached_validation = True
 
+    # This is a relatively slow operation. Do _not_ use in performance
+    # sensitive parts of the expression system code.
     def walk(self, allowed_types=None):
         """
-        Returns an iterator over this expression tree (depth-first). This is a
-        slow operation. Do _not_ use in performance sensitive code!
+        Returns an iterator over this expression tree (depth-first).
 
         Example::
 
@@ -856,6 +856,7 @@ def __init__(self, value, unit=None):
             else:
                 raise ValueError(
                     'Unit in myokit.Number should be a myokit.Unit or None.')
+
         # Create nice string representation
         self._str = myokit.float.str(self._value)
         if self._str[-2:] == '.0':
@@ -894,8 +895,10 @@ def _code(self, b, c):
 
     def convert(self, unit):
         """
-        Returns a copy of this number in a different unit. If the two units are
-        not compatible a :class:`myokit.IncompatibleUnitError` is raised.
+        Returns a copy of this number in a different unit.
+
+        If the two units are not compatible a
+        :class:`myokit.IncompatibleUnitError` is raised.
         """
         return Number(myokit.Unit.convert(self._value, self._unit, unit), unit)
 
@@ -1139,8 +1142,8 @@ def _validate(self, trail):
         # Check value: String is allowed at construction for debugging, but
         # not here!
         if not self._proper:
-            raise IntegrityError(
-                'Name value "' + repr(self._value) + '" is not an instance of'
+            raise myokit.IntegrityError(
+                f'Name value "{repr(self._value)}" is not an instance of'
                 ' class myokit.Variable', self._token)
 
     def var(self):
@@ -1161,8 +1164,9 @@ class Derivative(LhsExpression):
     def __init__(self, op):
         super().__init__((op,))
         if not isinstance(op, Name):
-            raise IntegrityError(
-                'The dot() operator can only be used on variables.',
+            raise myokit.TypeError(
+                'The dot() operator can only be used on variables (a'
+                ' myokit.Derivative requires a myokit.Name as argument).',
                 self._token)
         self._op = op
         self._proper = self._op._proper
@@ -1260,7 +1264,7 @@ def _validate(self, trail):
         # Check that value is a variable has already been performed by name
         # Check if value is the name of a state variable
         if not self._op._value.is_state():
-            raise IntegrityError(
+            raise myokit.IntegrityError(
                 'Derivatives can only be defined for state variables.',
                 self._token)
 
@@ -1280,15 +1284,17 @@ class PartialDerivative(LhsExpression):
     __hash__ = LhsExpression.__hash__
 
     def __init__(self, var1, var2):
+        super().__init__((var1, var2))
+
+        # Type checking
         if not isinstance(var1, (Name, Derivative)):
-            raise IntegrityError(
+            raise myokit.TypeError(
                 'The first argument to a partial derivative must be a'
-                ' variable name or a dot() expression.')
+                ' variable name or a dot() expression.', self._token)
         if not isinstance(var2, (Name, InitialValue)):
-            raise IntegrityError(
+            raise myokit.TypeError(
                 'The second argument to a partial derivative must be a'
-                ' variable name or an initial value.')
-        super().__init__((var1, var2))
+                ' variable name or an initial value.', self._token)
 
         self._var1 = var1
         self._var2 = var2
@@ -1392,10 +1398,11 @@ class InitialValue(LhsExpression):
 
     def __init__(self, var):
         super().__init__((var, ))
+
+        # Type checking
         if not isinstance(var, Name):
-            raise IntegrityError(
-                'The first argument to an initial value must be a variable'
-                ' name.', self._token)
+            raise myokit.TypeError('The argument to an initial value must be a'
+                                   ' variable name.', self._token)
 
         self._var = var
         self._references = set([self])
@@ -1454,7 +1461,7 @@ def _validate(self, trail):
         # Check if value is the name of a state variable
         var = self._var._value
         if not (isinstance(var, myokit.Variable) and var.is_state()):
-            raise IntegrityError(
+            raise myokit.IntegrityError(
                 'Initial values can only be defined for state variables.',
                 self._token)
 
@@ -1501,7 +1508,17 @@ def _tree_str(self, b, n):
         self._op._tree_str(b, n + self._treeDent)
 
 
-class PrefixPlus(PrefixExpression):
+class NumericalPrefixExpression(PrefixExpression):
+    """ Base class for expressions with a single numerical operand. """
+    def __init__(self, op):
+        super().__init__(op)
+        if isinstance(op, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid operand type: expected a numerical operand but'
+                f' found a condition ({type(op)}).', self._token)
+
+
+class PrefixPlus(NumericalPrefixExpression):
     """
     Prefixed plus. Indicates a positive number ``+op``.
 
@@ -1514,6 +1531,9 @@ class PrefixPlus(PrefixExpression):
     """
     _rep = '+'
 
+    def __init__(self, op):
+        super().__init__(op)
+
     def _diff(self, lhs, idstates):
         return self._op._diff(lhs, idstates)
 
@@ -1527,7 +1547,7 @@ def _polishb(self, b):
         self._op._polishb(b)
 
 
-class PrefixMinus(PrefixExpression):
+class PrefixMinus(NumericalPrefixExpression):
     """
     Prefixed minus. Indicates a negative number ``-op``.
 
@@ -1621,7 +1641,23 @@ def _tree_str(self, b, n):
         self._op2._tree_str(b, n + self._treeDent)
 
 
-class Plus(InfixExpression):
+class NumericalInfixExpression(InfixExpression):
+    """ Base class for infix expressions with numerical operands. """
+    def __init__(self, left, right):
+        super().__init__(left, right)
+        if isinstance(left, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for first operand: expected a numerical operand'
+                f' but found a condition ({type(left)}).',
+                self._token)
+        if isinstance(right, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for second operand: expected a numerical operand'
+                f' but found a condition ({type(left)}).',
+                self._token)
+
+
+class Plus(NumericalInfixExpression):
     """
     Represents the addition of two operands: ``left + right``.
 
@@ -1669,7 +1705,7 @@ def _eval_unit(self, mode):
             + unit1.clarify() + ' and ' + unit2.clarify() + '.')
 
 
-class Minus(InfixExpression):
+class Minus(NumericalInfixExpression):
     """
     Represents subtraction: ``left - right``.
 
@@ -1718,7 +1754,7 @@ def _eval_unit(self, mode):
             + unit1.clarify() + ' and ' + unit2.clarify() + '.')
 
 
-class Multiply(InfixExpression):
+class Multiply(NumericalInfixExpression):
     """
     Represents multiplication: ``left * right``.
 
@@ -1763,7 +1799,7 @@ def _eval_unit(self, mode):
         return unit1 * unit2
 
 
-class Divide(InfixExpression):
+class Divide(NumericalInfixExpression):
     """
     Represents division: ``left / right``.
 
@@ -1820,7 +1856,7 @@ def _eval_unit(self, mode):
         return unit1 / unit2
 
 
-class Quotient(InfixExpression):
+class Quotient(NumericalInfixExpression):
     """
     Represents the quotient of a division ``left // right``, also known as
     integer division.
@@ -1886,7 +1922,7 @@ def _eval_unit(self, mode):
         return unit1 / unit2
 
 
-class Remainder(InfixExpression):
+class Remainder(NumericalInfixExpression):
     """
     Represents the remainder of a division (the "modulo"), expressed in ``mmt``
     syntax as ``left % right``.
@@ -1964,7 +2000,7 @@ def _eval_unit(self, mode):
         return unit1
 
 
-class Power(InfixExpression):
+class Power(NumericalInfixExpression):
     """
     Represents exponentiation: ``left ^ right``.
 
@@ -2062,7 +2098,8 @@ class Function(Expression):
     has ``_nargs=[1]`` while :class:`Log` has ``_nargs=[1,2]``, showing that
     ``Log`` can be called with either ``1`` or ``2`` arguments.
 
-    If errors occur when creating a function, an IntegrityError may be thrown.
+    If errors occur when creating a function, a :class:`myokit.IntegrityError`
+    may be thrown.
 
     *Abstract class, extends:* :class:`Expression`
     """
@@ -2074,9 +2111,9 @@ def __init__(self, *ops):
         super().__init__(ops)
         if self._nargs is not None:
             if not len(ops) in self._nargs:
-                raise IntegrityError(
-                    'Function (' + str(self._fname) + ') created with wrong'
-                    ' number of arguments (' + str(len(ops)) + ', expecting '
+                raise myokit.IntegrityError(
+                    f'Function ({self._fname}) created with wrong number of'
+                    ' arguments ({len(ops)}, expecting '
                     + ' or '.join([str(x) for x in self._nargs]) + ').',
                     self._token)
 
@@ -2119,12 +2156,28 @@ def _tree_str(self, b, n):
             op._tree_str(b, n + self._treeDent)
 
 
-class UnaryDimensionlessFunction(Function):
+class UnaryNumericalFunction(Function):
     """
-    Function with a single operand that has dimensionless input and output.
+    Function with a single numerical operand and numerical output.
 
     *Abstract class, extends:* :class:`Function`
     """
+    def __init__(self, *ops):
+        super().__init__(*ops)
+        if isinstance(self._operands[0], myokit.Condition):
+            raise myokit.TypeError(
+                f'Function {self._fname}() expects a numerical operand, got a'
+                f' {type(self._operands[0])}.', self._token)
+
+
+class UnaryNumericalDimensionlessFunction(UnaryNumericalFunction):
+    """
+    Function with a single operand that has numerical, dimensionless input and
+    output.
+
+    *Abstract class, extends:* :class:`UnaryNumericalFunction`
+    """
+
     def _eval_unit(self, mode):
         unit = self._operands[0]._eval_unit(mode)
 
@@ -2142,7 +2195,7 @@ def _eval_unit(self, mode):
         return myokit.units.dimensionless
 
 
-class Sqrt(Function):
+class Sqrt(UnaryNumericalFunction):
     """
     Represents the square root ``sqrt(x)``.
 
@@ -2175,7 +2228,7 @@ def _eval_unit(self, mode):
         return unit ** 0.5
 
 
-class Sin(UnaryDimensionlessFunction):
+class Sin(UnaryNumericalDimensionlessFunction):
     """
     Represents the sine function ``sin(x)``.
 
@@ -2187,7 +2240,7 @@ class Sin(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 1))
     1.0
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'sin'
 
@@ -2205,7 +2258,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class Cos(UnaryDimensionlessFunction):
+class Cos(UnaryNumericalDimensionlessFunction):
     """
     Represents the cosine function ``cos(x)``.
 
@@ -2217,7 +2270,7 @@ class Cos(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 1))
     0.0
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'cos'
 
@@ -2235,7 +2288,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class Tan(UnaryDimensionlessFunction):
+class Tan(UnaryNumericalDimensionlessFunction):
     """
     Represents the tangent function ``tan(x)``.
 
@@ -2244,7 +2297,7 @@ class Tan(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 1))
     1.0
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'tan'
 
@@ -2262,7 +2315,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class ASin(UnaryDimensionlessFunction):
+class ASin(UnaryNumericalDimensionlessFunction):
     """
     Represents the inverse sine function ``asin(x)``.
 
@@ -2271,7 +2324,7 @@ class ASin(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 1))
     1.0
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'asin'
 
@@ -2289,7 +2342,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class ACos(UnaryDimensionlessFunction):
+class ACos(UnaryNumericalDimensionlessFunction):
     """
     Represents the inverse cosine ``acos(x)``.
 
@@ -2298,7 +2351,7 @@ class ACos(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 1))
     3.0
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'acos'
 
@@ -2319,7 +2372,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class ATan(UnaryDimensionlessFunction):
+class ATan(UnaryNumericalDimensionlessFunction):
     """
     Represents the inverse tangent function ``atan(x)``.
 
@@ -2333,7 +2386,7 @@ class ATan(UnaryDimensionlessFunction):
     (positive) x-axis. In this case, the returned value will be in the range
     (-pi, pi].
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'atan'
 
@@ -2351,7 +2404,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class Exp(UnaryDimensionlessFunction):
+class Exp(UnaryNumericalDimensionlessFunction):
     """
     Represents a power of *e*. Written ``exp(x)`` in ``.mmt`` syntax.
 
@@ -2360,7 +2413,7 @@ class Exp(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 4))
     2.7183
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'exp'
 
@@ -2400,6 +2453,20 @@ class Log(Function):
     _fname = 'log'
     _nargs = [1, 2]
 
+    def __init__(self, *ops):
+        super().__init__(*ops)
+        if isinstance(self._operands[0], myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for first operand: function log() expects'
+                f' numerical operands but found a {type(self._operands[0])}.',
+                self._token)
+        if len(self._operands) == 2 and isinstance(
+                self._operands[1], myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for second operand: function log() expects'
+                f' numerical operands but found a {type(self._operands[0])}.',
+                self._token)
+
     def _diff(self, lhs, idstates):
 
         if len(self._operands) == 1:
@@ -2487,7 +2554,7 @@ def _eval_unit(self, mode):
         return myokit.units.dimensionless
 
 
-class Log10(UnaryDimensionlessFunction):
+class Log10(UnaryNumericalDimensionlessFunction):
     """
     Represents the base-10 logarithm ``log10(x)``.
 
@@ -2496,7 +2563,7 @@ class Log10(UnaryDimensionlessFunction):
     >>> print(round(x.eval(), 1))
     2.0
 
-    *Extends:* :class:`UnaryDimensionlessFunction`
+    *Extends:* :class:`UnaryNumericalDimensionlessFunction`
     """
     _fname = 'log10'
 
@@ -2514,7 +2581,7 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
 
-class Floor(Function):
+class Floor(UnaryNumericalFunction):
     """
     Represents a rounding towards minus infinity ``floor(x)``.
 
@@ -2546,7 +2613,7 @@ def _eval_unit(self, mode):
         return self._operands[0]._eval_unit(mode)
 
 
-class Ceil(Function):
+class Ceil(UnaryNumericalFunction):
     """
     Represents a rounding towards positve infinity ``ceil(x)``.
 
@@ -2578,7 +2645,7 @@ def _eval_unit(self, mode):
         return self._operands[0]._eval_unit(mode)
 
 
-class Abs(Function):
+class Abs(UnaryNumericalFunction):
     """
     Returns the absolute value of a number ``abs(x)``.
 
@@ -2647,6 +2714,19 @@ def __init__(self, i, t, e):
         self._t = t     # then
         self._e = e     # else
 
+        if not isinstance(i, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for first operand: expected a condition but'
+                f' found {type(i)}.', self._token)
+        if isinstance(t, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for second operand: expected a numerical operand'
+                f' but found a condition ({type(t)}).', self._token)
+        if isinstance(e, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for third operand: expected a numerical operand'
+                f' but found a condition ({type(e)}).', self._token)
+
     def condition(self):
         """
         Returns this if-function's condition.
@@ -2694,16 +2774,14 @@ def _eval_unit(self, mode):
 
         # Mismatching units
         raise EvalUnitError(
-            self, 'Units of `then` and `else` part of an `if`'
-            ' must match. Got ' + str(unit2) + ' and ' + str(unit3) + '.')
+            self, 'Units of `then` and `else` part of an `if` must match.'
+            f' Got {unit2} and {unit3}.')
 
     def is_conditional(self):
         return True
 
     def piecewise(self):
-        """
-        Returns an equivalent ``Piecewise`` object.
-        """
+        """ Returns an equivalent ``Piecewise`` object. """
         return Piecewise(self._i, self._t, self._e)
 
     def value(self, which):
@@ -2753,11 +2831,11 @@ def __init__(self, *ops):
         # Check number of arguments
         n = len(self._operands)
         if n % 2 == 0:
-            raise IntegrityError(
+            raise myokit.IntegrityError(
                 'Piecewise function must have odd number of arguments:'
                 ' ([condition, value]+, else_value).', self._token)
         if n < 3:
-            raise IntegrityError(
+            raise myokit.IntegrityError(
                 'Piecewise function must have 3 or more arguments.',
                 self._token)
 
@@ -2771,6 +2849,19 @@ def __init__(self, *ops):
             self._e[i] = next(oper)
         self._e[m] = next(oper)
 
+        # Check argument types
+        for i, e in enumerate(self._i):
+            if not isinstance(e, myokit.Condition):
+                raise myokit.TypeError(
+                    f'operand at index {2 * i} must be a condition, but found'
+                    f' {type(e)}.', self._token)
+        for i, e in enumerate(self._e):
+            if isinstance(e, myokit.Condition):
+                j = 2 * i if i == len(self._e) - 1 else 1 + 2 * i
+                raise myokit.TypeError(
+                    f'operand at index {j} must be numerical, but found'
+                    f' {type(e)}.', self._token)
+
     def conditions(self):
         """
         Returns an iterator over the conditions used by this Piecewise.
@@ -2838,27 +2929,14 @@ def pieces(self):
 
 class Condition:
     """
-    *Abstract class*
-
-    Interface for conditional expressions that can be evaluated to True or
-    False. Doesn't add any methods but simply indicates that this is a
-    condition.
+    Base class for conditional expressions that evaluate to True or False.
     """
-
     def _diff(self, lhs, idstates):
         raise NotImplementedError(
             'Conditions do not have partial derivatives.')
 
 
-class PrefixCondition(PrefixExpression, Condition):
-    """
-    Interface for prefix conditions.
-
-    *Abstract class, extends:* :class:`Condition`, :class:`PrefixExpression`
-    """
-
-
-class Not(PrefixCondition):
+class Not(PrefixExpression, Condition):
     """
     Negates a condition: ``not x``.
 
@@ -2873,10 +2951,19 @@ class Not(PrefixCondition):
     >>> print(x.eval())
     True
 
-    *Extends:* :class:`PrefixCondition`
+    The operand to ``Not`` must be a condition.
+
+    *Extends:* :class:`PrefixExpression`, :class:`Condition`
     """
     _rep = 'not'
 
+    def __init__(self, op):
+        super().__init__(op)
+        if not isinstance(op, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid operand type: expected a condition but found a'
+                f' {type(op)}.', self._token)
+
     def _code(self, b, c):
         b.write('not ')
         brackets = self._op._rbp > LITERAL and self._op._rbp < self._rbp
@@ -2893,11 +2980,12 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
     def _eval_unit(self, mode):
-        unit = self._op._eval_unit(mode)
-        if unit not in (None, myokit.units.dimensionless):
-            raise EvalUnitError(
-                self, 'Operator `not` expects a dimensionless operand.')
-        return unit
+        # Make child check units
+        self._op._eval_unit(mode)
+
+        # No further checking necessary: operand is a condition so must return
+        # unitless.
+        return myokit.units.dimensionless
 
     def _polishb(self, b):
         b.write('not ')
@@ -2917,24 +3005,38 @@ class BinaryComparison(InfixCondition):
     """
     Base class for infix comparisons of two entities.
 
+    Takes numerical operands as input, but returns a condition.
+
     *Abstract class, extends:* :class:`InfixCondition`
     """
+    def __init__(self, left, right):
+        super().__init__(left, right)
+        if isinstance(left, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for first operand: expected a numerical operand'
+                f' but found a {type(left)}.',
+                self._token)
+        if isinstance(right, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for second operand: expected a numerical operand'
+                f' but found a {type(right)}.',
+                self._token)
+
     def _eval_unit(self, mode):
         unit1 = self._op1._eval_unit(mode)
         unit2 = self._op2._eval_unit(mode)
 
-        # Equal (including both None) is always ok
+        # Equal (including both None-or-dimensionless) is always ok.
         if unit1 == unit2:
-            return None if unit1 is None else myokit.units.dimensionless
+            return myokit.units.dimensionless
 
-        # In tolerant mode, a single None is OK
+        # In tolerant mode, we might still have a None, but this is OK too.
         if unit1 is None or unit2 is None:
             return myokit.units.dimensionless
 
         # Otherwise must match
-        raise EvalUnitError(
-            self, 'Condition ' + self._rep + ' requires equal units on both'
-            ' sides, got ' + str(unit1) + ' and ' + str(unit2) + '.')
+        raise EvalUnitError(self, f'Condition {self._rep} requires equal units'
+                            f' on both sides, got {unit1} and {unit2}.')
 
 
 class Equal(BinaryComparison):
@@ -3077,11 +3179,26 @@ class And(InfixCondition):
     >>> print(parse_expression('1 == 1 and 4 == 4').eval())
     True
 
+    Both operands must be a condition.
+
     *Extends:* :class:`InfixCondition`
     """
     _rbp = CONDITION_AND
     _rep = 'and'
 
+    def __init__(self, left, right):
+        super().__init__(left, right)
+        if not isinstance(left, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for first operand: expected a condition but'
+                f' found a {type(left)}.',
+                self._token)
+        if not isinstance(right, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for second operand: expected a condition but'
+                f' found a {type(right)}.',
+                self._token)
+
     def _eval(self, subst, precision):
         try:
             return (
@@ -3091,21 +3208,11 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
     def _eval_unit(self, mode):
-        unit1 = self._op1._eval_unit(mode)
-        unit2 = self._op2._eval_unit(mode)
-
-        # Propagate both None in tolerant mode
-        if unit1 is None and unit2 is None:
-            return None
-
-        # Ideal: both dimensionless
-        unit1 = myokit.units.dimensionless if unit1 is None else unit1
-        unit2 = myokit.units.dimensionless if unit2 is None else unit2
-        if unit1 == unit2 == myokit.units.dimensionless:
-            return unit1
-
-        raise EvalUnitError(
-            self, 'Operator `and` expects dimensionless operands.')
+        # Get children to check units: both must be conditions so result does
+        # not matter and no further checking is necessary.
+        self._op1._eval_unit(mode)
+        self._op2._eval_unit(mode)
+        return myokit.units.dimensionless
 
 
 class Or(InfixCondition):
@@ -3116,11 +3223,26 @@ class Or(InfixCondition):
     >>> print(parse_expression('1 == 1 or 2 == 4').eval())
     True
 
+    Both operands must be a condition.
+
     *Extends:* :class:`InfixCondition`
     """
     _rbp = CONDITION_AND
     _rep = 'or'
 
+    def __init__(self, left, right):
+        super().__init__(left, right)
+        if not isinstance(left, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for first operand: expected a condition but'
+                f' found a {type(left)}.',
+                self._token)
+        if not isinstance(right, myokit.Condition):
+            raise myokit.TypeError(
+                'Invalid type for second operand: expected a condition but'
+                f' found a {type(right)}.',
+                self._token)
+
     def _eval(self, subst, precision):
         try:
             return (
@@ -3130,21 +3252,11 @@ def _eval(self, subst, precision):
             raise EvalError(self, subst, e)
 
     def _eval_unit(self, mode):
-        unit1 = self._op1._eval_unit(mode)
-        unit2 = self._op2._eval_unit(mode)
-
-        # Propagate both None in tolerant mode
-        if unit1 is None and unit2 is None:
-            return None
-
-        # Ideal: both dimensionless
-        unit1 = myokit.units.dimensionless if unit1 is None else unit1
-        unit2 = myokit.units.dimensionless if unit2 is None else unit2
-        if unit1 == unit2 == myokit.units.dimensionless:
-            return unit1
-
-        raise EvalUnitError(
-            self, 'Operator `or` expects dimensionless operands.')
+        # Get children to check units: both must be conditions so result does
+        # not matter and no further checking is necessary.
+        self._op1._eval_unit(mode)
+        self._op2._eval_unit(mode)
+        return myokit.units.dimensionless
 
 
 class EvalError(Exception):
diff --git a/myokit/_model_api.py b/myokit/_model_api.py
index c21dfc68c..572e332a4 100644
--- a/myokit/_model_api.py
+++ b/myokit/_model_api.py
@@ -1098,9 +1098,7 @@ def check_units(self, mode=myokit.UNIT_TOLERANT):
                 raise myokit.IncompatibleUnitError(msg, var._token)
 
     def clone(self):
-        """
-        Returns a (deep) clone of this model.
-        """
+        """ Returns a (deep) clone of this model. """
         clone = Model()
 
         # Copy meta data
@@ -4523,16 +4521,10 @@ def promote(self, initial_value=0, state_value=None):
             warnings.warn('The keyword argument `state_value` is deprecated.'
                           ' Please use `initial_value` instead.')
 
-        # Handle string and number rhs's
-        model = self.model()
-        if not isinstance(initial_value, myokit.Expression):
-            if isinstance(initial_value, str):
-                # Expressions are evaluated in model context
-                initial_value = myokit.parse_expression(
-                    initial_value, context=model)
-            elif initial_value is not None:
-                initial_value = myokit.Number(initial_value)
+        # Parse initial value
+        initial_value = self._set_initial_value(initial_value, False)
 
+        model = self.model()
         try:
             # Set lhs to derivative expression
             self._lhs = myokit.Derivative(myokit.Name(self))
@@ -4857,6 +4849,9 @@ def set_rhs(self, rhs):
             x.set_rhs(myokit.Plus(myokit.Number(1), myokit.Name(y)))
             x.set_rhs('1 + y')
 
+        Expressions used as a variable's right-hand side must be numerical:
+        :class:`myokit.Condition` operators can not be used as RHS.
+
         Calling `set_rhs` will reset the validation status of the model this
         variable belongs to.
         """
diff --git a/myokit/_myokit_version.py b/myokit/_myokit_version.py
index 8d6c5d62c..af23585e5 100644
--- a/myokit/_myokit_version.py
+++ b/myokit/_myokit_version.py
@@ -14,7 +14,7 @@
 #    incompatibility
 #  - Changes to revision indicate bugfixes, tiny new features
 #  - There is no significance to odd/even numbers
-__version_tuple__ = 1, 36, 2
+__version_tuple__ = 1, 37, 0
 
 # String version of the version number
 __version__ = '.'.join([str(x) for x in __version_tuple__])
diff --git a/myokit/formats/__init__.py b/myokit/formats/__init__.py
index ed8e6cc4b..b537998c3 100644
--- a/myokit/formats/__init__.py
+++ b/myokit/formats/__init__.py
@@ -128,15 +128,7 @@ class ExpressionWriter:
        ``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 ``++``,
+    3. 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.
diff --git a/myokit/formats/ansic/_ewriter.py b/myokit/formats/ansic/_ewriter.py
index 3fae5dfa5..9eea7784a 100644
--- a/myokit/formats/ansic/_ewriter.py
+++ b/myokit/formats/ansic/_ewriter.py
@@ -102,23 +102,14 @@ def _ex_or(self, 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])}))'
+        return f'(!{self.ex(e[0])})'
 
     def _ex_if(self, e):
-        _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})'
+        return f'({self.ex(e._i)} ? {self.ex(e._t)} : {self.ex(e._e)})'
 
     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]
+        _ifs = [self.ex(x) for x in e._i]
         _thens = [self.ex(x) for x in e._e]
 
         s = []
@@ -200,13 +191,7 @@ def _ex_partial_derivative(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:
@@ -219,8 +204,7 @@ 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]
+        _ifs = [self.ex(x) for x in e._i]
         _thens = [self.ex(x) for x in e._e]
 
         s = []
diff --git a/myokit/formats/opencl/_ewriter.py b/myokit/formats/opencl/_ewriter.py
index 4d2b805b9..a88815f22 100644
--- a/myokit/formats/opencl/_ewriter.py
+++ b/myokit/formats/opencl/_ewriter.py
@@ -31,27 +31,6 @@ def __init__(self, precision=myokit.SINGLE_PRECISION, native_math=True):
         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_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):
-            return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
-        else:
-            return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
-
-    def _ex_infix_logical(self, e, op):
-        # 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):
@@ -126,25 +105,7 @@ def _ex_log10(self, e):
 
     #def _ex_and(self, e):
     #def _ex_or(self, 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):
-        # 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(_ifs)
-        for _if, _then in zip(_ifs, _thens):
-            s.append(f'({_if} ? {_then} : ')
-        s.append(_thens[-1])
-        s.append(')' * len(_ifs))
-        return ''.join(s)
+    #def _ex_not(self, e):
+    #def _ex_if(self, e):
+    #def _ex_piecewise(self, e):
 
diff --git a/myokit/formats/sympy/_ereader.py b/myokit/formats/sympy/_ereader.py
index 352b6f2f3..e6680bee1 100644
--- a/myokit/formats/sympy/_ereader.py
+++ b/myokit/formats/sympy/_ereader.py
@@ -156,7 +156,8 @@ def _ex_ceil(self, e):
     def _ex_abs(self, e):
         return myokit.Abs(self.ex(e.args[0]))
 
-    def _ex_not(self, e):
+    def _ex_not(self, e):   # pragma: no cover
+        # Sympy turns not(a == b) into a != b etc., so can't test!
         return myokit.Not(self.ex(e.args[0]))
 
     def _ex_equal(self, e):


Test patch:
diff --git a/myokit/tests/test_expressions.py b/myokit/tests/test_expressions.py
index 441ee351e..2080c0366 100755
--- a/myokit/tests/test_expressions.py
+++ b/myokit/tests/test_expressions.py
@@ -415,8 +415,8 @@ def test_is_conditional(self):
 
         pe = myokit.parse_expression
         self.assertFalse(pe('1 + 2 + 3').is_conditional())
-        self.assertTrue(pe('if(1, 0, 2)').is_conditional())
-        self.assertTrue(pe('1 + if(1, 0, 2)').is_conditional())
+        self.assertTrue(pe('if(1 == 2, 0, 2)').is_conditional())
+        self.assertTrue(pe('1 + if(1 == 1, 0, 2)').is_conditional())
 
     def test_pickling_error(self):
         # Tests pickling of expressions raises an exception
@@ -1097,8 +1097,11 @@ def test_basic(self):
 
         # Derivative of something other than a name: never allowed
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'on variables', myokit.Derivative,
-            myokit.Number(1))
+            myokit.TypeError, 'requires a myokit.Name',
+            myokit.Derivative, myokit.Number(1))
+        self.assertRaisesRegex(  # Not even another LhsExpression
+            myokit.TypeError, 'requires a myokit.Name',
+            myokit.Derivative, myokit.Derivative(myokit.Name(x)))
 
     def test_bracket(self):
         # Test Derivative.bracket()
@@ -1317,22 +1320,22 @@ def test_creation(self):
 
         # Others are not allowed
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'first argument to a partial',
+            myokit.TypeError, 'first argument to a partial',
             myokit.PartialDerivative, i, n)
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'first argument to a partial',
+            myokit.TypeError, 'first argument to a partial',
             myokit.PartialDerivative, myokit.Number(3), n)
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'first argument to a partial',
+            myokit.TypeError, 'first argument to a partial',
             myokit.PartialDerivative, myokit.PrefixPlus(n), n)
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'second argument to a partial',
+            myokit.TypeError, 'second argument to a partial',
             myokit.PartialDerivative, n, d)
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'second argument to a partial',
+            myokit.TypeError, 'second argument to a partial',
             myokit.PartialDerivative, n, myokit.Number(3))
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'second argument to a partial',
+            myokit.TypeError, 'second argument to a partial',
             myokit.PartialDerivative, n, myokit.PrefixPlus(n))
 
     def test_bracket(self):
@@ -1435,13 +1438,16 @@ def test_creation(self):
 
         # Value must be a name
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'first argument to an initial',
+            myokit.TypeError, 'argument to an initial value must be a variab',
             myokit.InitialValue, d)
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'first argument to an initial',
+            myokit.TypeError, 'argument to an initial value must be a variab',
+            myokit.InitialValue, i)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'argument to an initial value must be a variab',
             myokit.InitialValue, myokit.Number(3),)
         self.assertRaisesRegex(
-            myokit.IntegrityError, 'first argument to an initial',
+            myokit.TypeError, 'argument to an initial value must be a variab',
             myokit.InitialValue, myokit.PrefixPlus(n))
 
     def test_bracket(self):
@@ -1572,9 +1578,12 @@ def test_clone(self):
         self.assertEqual(y, myokit.PrefixPlus(j))
 
     def test_creation(self):
-        # Operand must be expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.PrefixPlus, 3)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'expected a numerical', myokit.PrefixPlus,
+            myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests PrefixPlus.diff()
@@ -1684,6 +1693,9 @@ def test_creation(self):
         # Operand must be an expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.PrefixMinus, 'test')
+        self.assertRaisesRegex(
+            myokit.TypeError, 'expected a numerical', myokit.PrefixMinus,
+            myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests PrefixMinus.diff()
@@ -1791,9 +1803,16 @@ def test_clone(self):
         self.assertEqual(y, myokit.Plus(i, i))
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Plus, 'toast', 4)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Plus, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Plus, myokit.Number(1), a)
 
     def test_diff(self):
         # Tests Plus.diff()
@@ -1913,9 +1932,16 @@ class MinusTest(unittest.TestCase):
     """Tests myokit.Minus."""
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Minus, 7, None)
+        a = myokit.NotEqual(myokit.Number(3), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Minus, a, myokit.Number(3))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Minus, myokit.Number(3), a)
 
     def test_diff(self):
         # Tests Minus.diff()
@@ -2019,6 +2045,13 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Multiply, 15, 3)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Multiply, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Multiply, myokit.Number(1), a)
 
     def test_diff(self):
         # Tests Multiply.diff()
@@ -2112,6 +2145,13 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Divide, 15, 3)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Divide, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Divide, myokit.Number(1), a)
 
     def test_diff(self):
         # Tests Divide.diff()
@@ -2205,6 +2245,13 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Quotient, 15, 3)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Quotient, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Quotient, myokit.Number(1), a)
 
     def test_diff(self):
         # Tests Quotient.diff()
@@ -2286,6 +2333,13 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Remainder, myokit.Number(3), 3)
+        a = myokit.MoreEqual(myokit.Number(1.1), myokit.Number(2.1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Remainder, a, myokit.Number(1.3))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Remainder, myokit.Number(-1.2), a)
 
     def test_diff(self):
         # Tests Remainder.diff()
@@ -2415,6 +2469,13 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Power, myokit.Number(3), 3)
+        a = myokit.Less(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Power, a, myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Power, myokit.Number(3.2), a)
 
     def test_diff(self):
         # Tests Power.diff()
@@ -2571,9 +2632,12 @@ def test_creation(self):
             myokit.IntegrityError, 'wrong number', myokit.Sqrt,
             myokit.Number(1), myokit.Number(2))
 
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Sqrt, False)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'sqrt\(\) expects a numerical operand',
+            myokit.Sqrt, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Sqrt.diff()
@@ -2682,9 +2746,12 @@ def test_creation(self):
             myokit.IntegrityError, 'wrong number', myokit.Exp,
             myokit.Number(1), myokit.Number(2))
 
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Exp, 1.2)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'Function exp\(\) expects a numerical operand',
+            myokit.Exp, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Exp.diff()
@@ -2838,11 +2905,21 @@ def test_creation(self):
             myokit.IntegrityError, 'wrong number', myokit.Log,
             myokit.Number(1), myokit.Number(2), myokit.Number(3))
 
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Log, 1.2)
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Log, myokit.Number(3), 1.2)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, r'first operand: function log\(\) expects numer',
+            myokit.Log, a)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'first operand: function log\(\) expects numer',
+            myokit.Log, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, r'second operand: function log\(\) expects nume',
+            myokit.Log, myokit.Number(1), a)
 
     def test_diff(self):
         # Tests Log.diff()
@@ -2974,9 +3051,12 @@ class Log10Test(unittest.TestCase):
     """Tests myokit.Log10."""
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Log10, 1.2)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'log10\(\) expects a numerical operand',
+            myokit.Log10, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Log10.diff()
@@ -3009,9 +3089,12 @@ class SinTest(unittest.TestCase):
     """Tests myokit.Sin."""
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Sin, 2.1)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'sin\(\) expects a numerical operand',
+            myokit.Sin, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Sin.diff()
@@ -3043,9 +3126,12 @@ class CosTest(unittest.TestCase):
     """Tests myokit.Cos."""
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Cos, myokit.Cos)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'cos\(\) expects a numerical operand',
+            myokit.Cos, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Cos.diff()
@@ -3076,9 +3162,12 @@ class TanTest(unittest.TestCase):
     """ Tests myokit.Tan. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Tan, myokit.Model())
+        self.assertRaisesRegex(
+            myokit.TypeError, r'tan\(\) expects a numerical operand',
+            myokit.Tan, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Tan.diff()
@@ -3109,9 +3198,12 @@ class ASinTest(unittest.TestCase):
     """ Tests myokit.ASin. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.ASin, 4)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'asin\(\) expects a numerical operand',
+            myokit.ASin, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests ASin.diff()
@@ -3143,9 +3235,12 @@ class ACosTest(unittest.TestCase):
     """ Tests myokit.ACos. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.ACos, 4)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'acos\(\) expects a numerical operand',
+            myokit.ACos, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests ACos.diff()
@@ -3178,9 +3273,12 @@ class ATanTest(unittest.TestCase):
     """ Tests myokit.ATan. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.ATan, 4)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'atan\(\) expects a numerical operand',
+            myokit.ATan, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests ATan.diff()
@@ -3213,9 +3311,13 @@ class FloorTest(unittest.TestCase):
     """ Tests myokit.Floor. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Floor, 1.2)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, r'floor\(\) expects a numerical operand',
+            myokit.Floor, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Floor.diff()
@@ -3269,9 +3371,12 @@ class CeilTest(unittest.TestCase):
     """ Tests myokit.Ceil. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Ceil, 3.4)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'ceil\(\) expects a numerical operand',
+            myokit.Ceil, myokit.LessEqual(myokit.Number(3), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Ceil.diff()
@@ -3325,9 +3430,12 @@ class AbsTest(unittest.TestCase):
     """ Tests myokit.Abs. """
 
     def test_creation(self):
-        # Operand must be an expression
+        # Operand must be a numerical expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Abs, 3.4)
+        self.assertRaisesRegex(
+            myokit.TypeError, r'abs\(\) expects a numerical operand',
+            myokit.Abs, myokit.Equal(myokit.Number(1), myokit.Number(2)))
 
     def test_diff(self):
         # Tests Abs.diff()
@@ -3402,9 +3510,16 @@ class EqualTest(unittest.TestCase):
     """ Tests myokit.Equal. """
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Equal, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Equal, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Equal, myokit.Number(1), a)
 
     def test_diff(self):
         # Tests Equal.diff()
@@ -3424,62 +3539,54 @@ def test_eval(self):
     def test_eval_unit(self):
         # Test Equal.eval_unit().
 
-        # Mini model
-        m = myokit.Model()
-        c = m.add_component('c')
-        x = c.add_variable('x')
-        x.set_rhs('3')
-        y = c.add_variable('y')
-        y.set_rhs('3')
-        z = c.add_variable('z')
-        z.set_rhs('x == y')
-
-        # Test in tolerant mode
-        self.assertEqual(z.rhs().eval_unit(), None)
-        x.set_unit(myokit.units.ampere)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        y.set_unit(myokit.units.ampere)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        y.set_unit(myokit.units.volt)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'equal units', z.rhs().eval_unit)
-        x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        y.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), None)
-
-        # Test in strict mode
+        # None and None is always fine
         s = myokit.UNIT_STRICT
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        x.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'equal units', z.rhs().eval_unit, s)
-        y.set_unit(myokit.units.ampere)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        y.set_unit(myokit.units.volt)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'equal units', z.rhs().eval_unit)
-        x.set_unit(None)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'equal units', z.rhs().eval_unit, s)
-        y.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
+        e = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        d = myokit.units.dimensionless
+        self.assertEqual(e.eval_unit(), d)
+        self.assertEqual(e.eval_unit(s), d)
+        # None and dimensionless is always fine
+        e = myokit.Equal(myokit.Number(1), myokit.Number(2, d))
+        self.assertEqual(e.eval_unit(), d)
+        self.assertEqual(e.eval_unit(s), d)
+        # Equal is always fine
+        e = myokit.Equal(myokit.Number(1, d), myokit.Number(2, d))
+        self.assertEqual(e.eval_unit(), d)
+        self.assertEqual(e.eval_unit(s), d)
+        a = myokit.units.ampere
+        e = myokit.Equal(myokit.Number(1, a), myokit.Number(2, a))
+        self.assertEqual(e.eval_unit(), d)
+        self.assertEqual(e.eval_unit(s), d)
+        # Unequal is never fine
+        e = myokit.Equal(myokit.Number(1, d), myokit.Number(2, a))
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit)
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit, s)
+        # One None one not-dimensionless is only fine in tolerant mode
+        e = myokit.Equal(myokit.Number(1, a), myokit.Number(2))
+        self.assertEqual(e.eval_unit(), d)
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit, s)
 
     def test_tree_str(self):
         # Test Equal.tree_str().
         x = myokit.Equal(myokit.Number(1), myokit.Number(2))
         self.assertEqual(x.tree_str(), '==\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  ==\n    1\n    2\n')
+        self.assertEqual(myokit.Not(x).tree_str(), 'not\n  ==\n    1\n    2\n')
 
 
 class NotEqualTest(unittest.TestCase):
     """ Tests myokit.NotEqual. """
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.NotEqual, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.NotEqual, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.NotEqual, myokit.Number(1), a)
 
     def test_eval(self):
         # Test NotEqual.eval().
@@ -3492,17 +3599,24 @@ def test_tree_str(self):
         # Test NotEqual.tree_str().
         x = myokit.NotEqual(myokit.Number(1), myokit.Number(2))
         self.assertEqual(x.tree_str(), '!=\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  !=\n    1\n    2\n')
+        self.assertEqual(myokit.Not(x).tree_str(),
+                         'not\n  !=\n    1\n    2\n')
 
 
 class MoreTest(unittest.TestCase):
     """ Tests myokit.More. """
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.More, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.More, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.More, myokit.Number(1), a)
 
     def test_eval(self):
         # Test More.eval().
@@ -3515,8 +3629,7 @@ def test_tree_str(self):
         # Test More.tree_str().
         x = myokit.More(myokit.Number(1), myokit.Number(2))
         self.assertEqual(x.tree_str(), '>\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  >\n    1\n    2\n')
+        self.assertEqual(myokit.Not(x).tree_str(), 'not\n  >\n    1\n    2\n')
 
 
 class LessTest(unittest.TestCase):
@@ -3526,6 +3639,13 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Less, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.Less, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.Less, myokit.Number(1), a)
 
     def test_eval(self):
         # Test Less.eval().
@@ -3538,17 +3658,23 @@ def test_tree_str(self):
         # Test Less.tree_str().
         x = myokit.Less(myokit.Number(1), myokit.Number(2))
         self.assertEqual(x.tree_str(), '<\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  <\n    1\n    2\n')
+        self.assertEqual(myokit.Not(x).tree_str(), 'not\n  <\n    1\n    2\n')
 
 
 class MoreEqualTest(unittest.TestCase):
     """ Tests myokit.MoreEqual. """
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.MoreEqual, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.MoreEqual, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.MoreEqual, myokit.Number(1), a)
 
     def test_eval(self):
         # Test MoreEqual.eval().
@@ -3563,17 +3689,23 @@ def test_tree_str(self):
         # Test MoreEqual.tree_str().
         x = myokit.MoreEqual(myokit.Number(1), myokit.Number(2))
         self.assertEqual(x.tree_str(), '>=\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  >=\n    1\n    2\n')
+        self.assertEqual(myokit.Not(x).tree_str(), 'not\n  >=\n    1\n    2\n')
 
 
 class LessEqualTest(unittest.TestCase):
     """ Tests myokit.LessEqual. """
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be numerical expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.LessEqual, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a numerical',
+            myokit.LessEqual, a, myokit.Number(1))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a numerical',
+            myokit.LessEqual, myokit.Number(1), a)
 
     def test_eval(self):
         # Test LessEqual.eval().
@@ -3588,17 +3720,23 @@ def test_tree_str(self):
         # Test LessEqual.tree_str().
         x = myokit.LessEqual(myokit.Number(1), myokit.Number(2))
         self.assertEqual(x.tree_str(), '<=\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  <=\n    1\n    2\n')
+        self.assertEqual(myokit.Not(x).tree_str(), 'not\n  <=\n    1\n    2\n')
 
 
 class AndTest(unittest.TestCase):
     """ Tests myokit.And. """
 
     def test_creation(self):
-        # Operands must be expressions
+        # Operands must be conditional expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.And, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a condition',
+            myokit.And, myokit.Number(1), a)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a condition',
+            myokit.And, a, myokit.Number(1))
 
     def test_diff(self):
         # Tests And.diff()
@@ -3613,67 +3751,45 @@ def test_diff(self):
 
     def test_eval(self):
         # Test And.eval().
-        x = myokit.And(myokit.Number(1), myokit.Number(1))
-        self.assertTrue(x.eval())
-        x = myokit.And(myokit.Number(0), myokit.Number(2))
-        self.assertFalse(x.eval())
+        a = myokit.Equal(myokit.Number(2), myokit.Number(2))
+        b = myokit.Equal(myokit.Number(3), myokit.Number(1))
+        self.assertTrue(myokit.And(a, a).eval())
+        self.assertFalse(myokit.And(a, b).eval())
+        self.assertFalse(myokit.And(b, b).eval())
 
     def test_eval_unit(self):
         # Test And.eval_unit().
 
-        # Mini model
-        m = myokit.Model()
-        c = m.add_component('c')
-        x = c.add_variable('x')
-        x.set_rhs('1')
-        y = c.add_variable('y')
-        y.set_rhs('1')
-        z = c.add_variable('z')
-        z.set_rhs('x and y')
-
-        # Test in tolerant mode
-        self.assertEqual(z.rhs().eval_unit(), None)
-        x.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
-        y.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
-        x.set_unit(myokit.units.dimensionless)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
-        y.set_unit(myokit.units.dimensionless)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        y.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), None)
-
-        # Test in strict mode
+        # Test in tolerant mode: no own testing, but should test operands!
+        e = myokit.parse_expression('1 == 1 and 2 == 2')
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 [1] and 2 [1] == 2')
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 [1] == 1 [1] and 2 [1] == 2 [1]')
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 [1] == 1 [1] and 2 [1] == 2 [mg]')
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit)
+
+        # Test in strict mode: no own testing, but should test operands!
         s = myokit.UNIT_STRICT
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        x.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
-        y.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
-        x.set_unit(myokit.units.dimensionless)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
-        y.set_unit(myokit.units.dimensionless)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        y.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 and 2 == 2')
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 [1] and 2 [1] == 2')
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 [1] == 1 [1] and 2 [1] == 2 [1]')
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 [V] and 1 == 1 [1]')
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit, s)
 
     def test_tree_str(self):
         # Test And.tree_str().
-        x = myokit.And(myokit.Number(1), myokit.Number(2))
-        self.assertEqual(x.tree_str(), 'and\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  and\n    1\n    2\n')
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        b = myokit.Less(myokit.Number(4), myokit.Number(3))
+        self.assertEqual(myokit.And(a, b).tree_str(),
+                         'and\n  ==\n    1\n    2\n  <\n    4\n    3\n')
+        self.assertEqual(
+            myokit.Not(myokit.And(b, a)).tree_str(),
+            'not\n  and\n    <\n      4\n      3\n    ==\n      1\n      2\n')
 
 
 class OrTest(unittest.TestCase):
@@ -3683,72 +3799,58 @@ def test_creation(self):
         # Operands must be expressions
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Or, 1, 1)
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand: expected a condition',
+            myokit.Or, myokit.Number(1), a)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand: expected a condition',
+            myokit.Or, a, myokit.Number(1))
 
     def test_eval(self):
         # Test Or.eval().
-        x = myokit.Or(myokit.Number(1), myokit.Number(1))
-        self.assertTrue(x.eval())
-        x = myokit.Or(myokit.Number(0), myokit.Number(2))
-        self.assertTrue(x.eval())
-        x = myokit.Or(myokit.Number(0), myokit.Number(0))
-        self.assertFalse(x.eval())
+        a = myokit.Equal(myokit.Number(2), myokit.Number(2))
+        b = myokit.Equal(myokit.Number(3), myokit.Number(1))
+        self.assertTrue(myokit.Or(a, a).eval())
+        self.assertTrue(myokit.Or(a, b).eval())
+        self.assertFalse(myokit.Or(b, b).eval())
 
     def test_eval_unit(self):
         # Test Or.eval_unit().
 
-        # Mini model
-        m = myokit.Model()
-        c = m.add_component('c')
-        x = c.add_variable('x')
-        x.set_rhs('1')
-        y = c.add_variable('y')
-        y.set_rhs('1')
-        z = c.add_variable('z')
-        z.set_rhs('x or y')
-
-        # Test in tolerant mode
-        self.assertEqual(z.rhs().eval_unit(), None)
-        x.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
-        y.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
-        x.set_unit(myokit.units.dimensionless)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
-        y.set_unit(myokit.units.dimensionless)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
-        y.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), None)
-
-        # Test in strict mode
+        # Test in tolerant mode: no own testing, but should test operands!
+        e = myokit.parse_expression('1 == 1 or 2 == 2')
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 [1] or 2 [1] == 2')
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 [1] == 1 [1] or 2 [1] == 2 [1]')
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 [1] == 1 [1] or 2 [1] == 2 [mg]')
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit)
+
+        # Test in strict mode: no own testing, but should test operands!
         s = myokit.UNIT_STRICT
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        x.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
-        y.set_unit(myokit.units.ampere)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
-        x.set_unit(myokit.units.dimensionless)
-        self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
-        y.set_unit(myokit.units.dimensionless)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
-        y.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 or 2 == 2')
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 [1] or 2 [1] == 2')
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 [1] == 1 [1] or 2 [1] == 2 [1]')
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
+        e = myokit.parse_expression('1 == 1 [A] or 2 [1] == 2 [1]')
+        self.assertRaises(myokit.IncompatibleUnitError, e.eval_unit, s)
 
     def test_tree_str(self):
         # Test Or.tree_str().
-        x = myokit.Or(myokit.Number(1), myokit.Number(2))
-        self.assertEqual(x.tree_str(), 'or\n  1\n  2\n')
-        x = myokit.Plus(myokit.Number(3), x)
-        self.assertEqual(x.tree_str(), '+\n  3\n  or\n    1\n    2\n')
+        a = myokit.Equal(myokit.Number(1), myokit.Number(2))
+        b = myokit.NotEqual(myokit.Number(3), myokit.Number(4))
+        x = myokit.Or(a, b)
+        self.assertEqual(
+            x.tree_str(),
+            'or\n  ==\n    1\n    2\n  !=\n    3\n    4\n')
+        x = myokit.Not(x)
+        self.assertEqual(
+            x.tree_str(),
+            'not\n  or\n    ==\n      1\n      2\n    !=\n      3\n      4\n')
 
 
 class NotTest(unittest.TestCase):
@@ -3758,11 +3860,12 @@ def test_creation(self):
         # Operand must be an expression
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Not, 1)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'expected a condition',
+            myokit.Not, myokit.Number(1))
 
     def test_code(self):
         # Test Not.code().
-        x = myokit.Not(myokit.Number(1))
-        self.assertEqual(x.code(), 'not 1')
         x = myokit.Not(myokit.Equal(myokit.Number(1), myokit.Number(1)))
         self.assertEqual(x.code(), 'not (1 == 1)')
 
@@ -3776,9 +3879,9 @@ def test_diff(self):
 
     def test_eval(self):
         # Test Not.eval().
-        x = myokit.Not(myokit.Number(1))
+        x = myokit.Not(myokit.Equal(myokit.Number(1), myokit.Number(1)))
         self.assertFalse(x.eval())
-        x = myokit.Not(myokit.Number(0))
+        x = myokit.Not(myokit.Equal(myokit.Number(1), myokit.Number(0)))
         self.assertTrue(x.eval())
 
     def test_eval_unit(self):
@@ -3787,36 +3890,34 @@ def test_eval_unit(self):
         # Mini model
         m = myokit.Model()
         c = m.add_component('c')
-        x = c.add_variable('x')
-        x.set_rhs('1')
-        z = c.add_variable('z')
-        z.set_rhs('not x')
+        x = c.add_variable('x', rhs=1)
+        y = c.add_variable('y', rhs=2)
+        e = myokit.parse_expression('not (x != y)', context=c)
 
         # Test in tolerant mode
-        self.assertEqual(z.rhs().eval_unit(), None)
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
         x.set_unit(myokit.units.ampere)
+        y.set_unit(myokit.units.dimensionless)
         self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionless', z.rhs().eval_unit)
+            myokit.IncompatibleUnitError, 'equal units on both', e.eval_unit)
         x.set_unit(myokit.units.dimensionless)
-        self.assertEqual(z.rhs().eval_unit(), myokit.units.dimensionless)
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
         x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(), None)
+        self.assertEqual(e.eval_unit(), myokit.units.dimensionless)
 
         # Test in strict mode
         s = myokit.UNIT_STRICT
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
         x.set_unit(myokit.units.ampere)
         self.assertRaisesRegex(
-            myokit.IncompatibleUnitError, 'dimensionles', z.rhs().eval_unit, s)
+            myokit.IncompatibleUnitError, 'equal units on bot', e.eval_unit, s)
         x.set_unit(myokit.units.dimensionless)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
         x.set_unit(None)
-        self.assertEqual(z.rhs().eval_unit(s), myokit.units.dimensionless)
+        self.assertEqual(e.eval_unit(s), myokit.units.dimensionless)
 
     def test_polish(self):
         # Test Not._polish().
-        x = myokit.Not(myokit.Number(1))
-        self.assertEqual(x._polish(), 'not 1')
         x = myokit.Not(myokit.Equal(myokit.Number(1), myokit.Number(1)))
         self.assertEqual(x._polish(), 'not == 1 1')
 
@@ -3838,9 +3939,15 @@ def test_creation(self):
         # Test is_conditional()
         self.assertTrue(if_.is_conditional())
 
-        # Operands must be expressions
+        # Operands must be expressions and have right types
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.If, 1, 1, 1)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'first operand', myokit.If, then, then, then)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'second operand', myokit.If, cond, cond, then)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'third operand', myokit.If, cond, then, cond)
 
     def test_diff(self):
         # Tests If.diff()
@@ -3887,14 +3994,12 @@ def test_eval_unit(self):
         # Mini model
         m = myokit.Model()
         c = m.add_component('c')
-        v1 = c.add_variable('v1')
         v2 = c.add_variable('v2')
         v3 = c.add_variable('v3')
         v4 = c.add_variable('v4')
-        v1.set_rhs('1 == 1')
         v2.set_rhs(2)
         v3.set_rhs(3)
-        v4.set_rhs('if(v1, v2, v3)')
+        v4.set_rhs('if(1 == 1, v2, v3)')
         z = v4.rhs()
 
         # Test in tolerant mode
@@ -4011,6 +4116,26 @@ def test_creation(self):
         self.assertRaisesRegex(myokit.IntegrityError, 'must be other Express',
                                myokit.Piecewise, 1, 1, 1)
 
+        # Operands must have right types
+        self.assertRaisesRegex(
+            myokit.TypeError, 'operand at index 0 must be a condition',
+            myokit.Piecewise, then1, then2, then3)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'operand at index 1 must be numerical',
+            myokit.Piecewise, cond1, cond1, then1)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'operand at index 2 must be numerical',
+            myokit.Piecewise, cond1, then1, cond2)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'operand at index 2 must be a condition',
+            myokit.Piecewise, cond1, then1, then2, then3, then1)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'operand at index 3 must be numerical',
+            myokit.Piecewise, cond1, then1, cond2, cond3, then2)
+        self.assertRaisesRegex(
+            myokit.TypeError, 'operand at index 4 must be numerical',
+            myokit.Piecewise, cond1, then1, cond2, then2, cond3)
+
     def test_diff(self):
         # Tests Piecewise.diff()
 
@@ -4078,24 +4203,13 @@ def test_eval_unit(self):
         # Mini model
         m = myokit.Model()
         comp = m.add_component('comp')
-
-        # Create conditions
-        c1 = comp.add_variable('c1')
-        c2 = comp.add_variable('c2')
-        c1.set_rhs('1 == 2')
-        c2.set_rhs('1 == 2')
-
-        # Create values
-        t1 = comp.add_variable('t1')
-        t2 = comp.add_variable('t2')
-        t3 = comp.add_variable('t3')
-        t1.set_rhs(1)
-        t2.set_rhs(2)
-        t3.set_rhs(3)
+        t1 = comp.add_variable('t1', rhs=1)
+        t2 = comp.add_variable('t2', rhs=2)
+        t3 = comp.add_variable('t3', rhs=3)
 
         # Create piecewise
         pw = comp.add_variable('pw')
-        pw.set_rhs('piecewise(c1, t1, c2, t2, t3)')
+        pw.set_rhs('piecewise(1 == 2, t1, 1 == 2, t2, t3)')
         z = pw.rhs()
 
         # Test in tolerant mode
diff --git a/myokit/tests/test_formats_ansic.py b/myokit/tests/test_formats_ansic.py
index 2fca66265..2a613dd8a 100755
--- a/myokit/tests/test_formats_ansic.py
+++ b/myokit/tests/test_formats_ansic.py
@@ -176,11 +176,13 @@ def test_functions(self):
 
     def test_conditions(self):
         a, b, c, d = self.abcd
+        p = myokit.Equal(a, b)
+        q = myokit.NotEqual(c, d)
 
-        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(And(p, q), '((a == b) && (c != d))')
+        self.eq(Or(q, p), '((c != d) || (a == b))')
+        self.eq(Not(And(p, q)), '(!((a == b) && (c != d)))')
+        self.eq(Not(p), '(!(a == b))')
 
         self.eq(Equal(a, b), '(a == b)')
         self.eq(NotEqual(a, b), '(a != b)')
@@ -191,12 +193,6 @@ def test_conditions(self):
 
         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):
 
@@ -206,11 +202,6 @@ def test_conditionals(self):
         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())
@@ -329,9 +320,6 @@ def run(self):
         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)
diff --git a/myokit/tests/test_formats_cpp.py b/myokit/tests/test_formats_cpp.py
index b654055fb..8fef3f4ac 100755
--- a/myokit/tests/test_formats_cpp.py
+++ b/myokit/tests/test_formats_cpp.py
@@ -72,11 +72,6 @@ def test_conditionals(self):
                                  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())
diff --git a/myokit/tests/test_formats_cuda.py b/myokit/tests/test_formats_cuda.py
index 5bb95531e..31ee5bc76 100755
--- a/myokit/tests/test_formats_cuda.py
+++ b/myokit/tests/test_formats_cuda.py
@@ -186,10 +186,12 @@ 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))')
+        p = Equal(a, b)
+        q = NotEqual(c, d)
+        self.eq(And(p, q), '((a == b) && (c != d))')
+        self.eq(Or(q, p), '((c != d) || (a == b))')
+        self.eq(Not(And(p, p)), '(!((a == b) && (a == b)))')
+        self.eq(Not(q), '(!(c != d))')
 
         self.eq(Equal(a, b), '(a == b)')
         self.eq(NotEqual(a, b), '(a != b)')
@@ -198,14 +200,9 @@ def test_conditions(self):
         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(Equal(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
@@ -216,11 +213,6 @@ def test_conditionals(self):
         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 58c29a7d6..f02f59cd2 100755
--- a/myokit/tests/test_formats_easyml.py
+++ b/myokit/tests/test_formats_easyml.py
@@ -400,16 +400,14 @@ def test_functions(self):
     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(Not(NotEqual(a, b)), '(!(a != b))')
         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(Or(Equal(Number(1), Number(2)), Equal(Number(3), Number(4)))),
+            '(!((1.0 == 2.0) || (3.0 == 4.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
@@ -418,9 +416,6 @@ def test_conditionals(self):
         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__':
diff --git a/myokit/tests/test_formats_latex.py b/myokit/tests/test_formats_latex.py
index 14a8b7ef9..14f805298 100755
--- a/myokit/tests/test_formats_latex.py
+++ b/myokit/tests/test_formats_latex.py
@@ -179,10 +179,6 @@ def test_functions(self):
     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)')
@@ -193,18 +189,21 @@ def test_conditions(self):
         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(Or(Equal(a, b), NotEqual(c, d)),
+                r'\left(\left(\text{a}=\text{b}\right)\or'
+                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)')
+        p = myokit.Equal(a, b)
+        q = myokit.NotEqual(c, d)
+        self.eq(myokit.If(p, c, d), r'\text{if}\left(\left(\text{a}=\text{b}'
+                r'\right),\text{c},\text{d}\right)')
+        self.eq(myokit.Piecewise(p, b, q, d, Number(1)), r'\text{piecewise}'
+                r'\left(\left(\text{a}=\text{b}\right),\text{b},\left(\text{c}'
+                r'\neq\text{d}\right),\text{d},1.0\right)')
 
 
 if __name__ == '__main__':
diff --git a/myokit/tests/test_formats_matlab.py b/myokit/tests/test_formats_matlab.py
index 4283b521b..845964869 100755
--- a/myokit/tests/test_formats_matlab.py
+++ b/myokit/tests/test_formats_matlab.py
@@ -177,10 +177,6 @@ def test_functions(self):
     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)')
@@ -191,10 +187,6 @@ def test_conditions(self):
         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
diff --git a/myokit/tests/test_formats_opencl.py b/myokit/tests/test_formats_opencl.py
index 92dca353f..546ac56c2 100755
--- a/myokit/tests/test_formats_opencl.py
+++ b/myokit/tests/test_formats_opencl.py
@@ -170,16 +170,8 @@ def test_functions_no_native(self):
         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)')
@@ -189,23 +181,7 @@ def test_conditions(self):
 
         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
@@ -216,11 +192,6 @@ def test_conditionals(self):
         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__':
     unittest.main()
diff --git a/myokit/tests/test_formats_python.py b/myokit/tests/test_formats_python.py
index a2046d1b6..543de35a8 100755
--- a/myokit/tests/test_formats_python.py
+++ b/myokit/tests/test_formats_python.py
@@ -232,10 +232,6 @@ def test_functions(self):
     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)')
@@ -246,7 +242,6 @@ def test_conditions(self):
         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)
@@ -267,11 +262,6 @@ def test_conditions(self):
         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
@@ -501,10 +491,6 @@ def test_functions(self):
     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)')
@@ -517,8 +503,6 @@ def test_conditions(self):
         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)
@@ -532,9 +516,8 @@ def test_conditions(self):
         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])
+        self.py(Or(true, false), True)
+        self.py(Not(true), False)
 
     def test_conditionals(self):
         a, b, c, d = self.abcd
diff --git a/myokit/tests/test_formats_stan.py b/myokit/tests/test_formats_stan.py
index 89e50aeeb..2e024c167 100755
--- a/myokit/tests/test_formats_stan.py
+++ b/myokit/tests/test_formats_stan.py
@@ -139,10 +139,6 @@ def test_functions(self):
     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)')
@@ -153,10 +149,6 @@ def test_conditions(self):
         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
@@ -165,11 +157,6 @@ def test_conditionals(self):
         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 cae6531cd..d017ffb38 100755
--- a/myokit/tests/test_formats_sympy.py
+++ b/myokit/tests/test_formats_sympy.py
@@ -237,10 +237,10 @@ def test_reader_writer(self):
         self.assertEqual(r.ex(cx), x)
 
         # Not
-        x = myokit.Not(a)
-        cx = sp.Not(ca)
+        x = myokit.Not(x)
+        cx = sp.Not(cx)
         self.assertEqual(w.ex(x), cx)
-        self.assertEqual(r.ex(cx), x)
+        self.assertEqual(r.ex(cx), myokit.More(a, b))  # Can't test!
 
         # And
         cond1 = myokit.More(a, b)
diff --git a/myokit/tests/test_parsing.py b/myokit/tests/test_parsing.py
index dcea7cfa3..e5ef888e9 100755
--- a/myokit/tests/test_parsing.py
+++ b/myokit/tests/test_parsing.py
@@ -697,6 +697,25 @@ def test_parse_variable(self):
         self.assertRaisesRegex(
             myokit.ParseError, 'Duplicate variable unit', p, code)
 
+        # Invalid use of condition
+        code = (
+            '[[model]]',
+            '[x]',
+            't = 0 bind time',
+            'x = 3 == 2',
+        )
+        self.assertRaisesRegex(
+            myokit.ParseError, 'not be a condition', p, code)
+        code = (
+            '[[model]]',
+            'x.y = 1 < 2',
+            '[x]',
+            't = 0 bind time',
+            'dot(y) = 1',
+        )
+        self.assertRaisesRegex(
+            myokit.ParseError, 'not be a condition', p, code)
+
     def test_parse_unit(self):
         # Test :meth:`parse_unit` and :meth:`parse_unit_string`.
         from myokit._parsing import parse_unit_string as p