diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index 667edc4b..b4d2d22b 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -1,15 +1,8 @@
 ## pending/current ##
 
-- General:
-    - Introduce qupulse.utils.isclose (an alias for math.isclose if available)
-
-- Pulse Templates:
-    - `AtomicMultichannelPulseTemplate`:
-        - Add duration keyword argument & example (see MultiChannelTemplates notebook)
-        - Make duration equality check approximate (numeric tolerance)
-
 - Expressions:
     - Make ExpressionScalar hashable
+    - Fix bug that prevented evaluation of expressions containing some special functions (`erfc`, `factorial`, etc.)
 
 ## 0.2 ##
 
diff --git a/doc/source/examples/07MultiChannelTemplates.ipynb b/doc/source/examples/07MultiChannelTemplates.ipynb
index b0dd1e48..4110c07e 100644
--- a/doc/source/examples/07MultiChannelTemplates.ipynb
+++ b/doc/source/examples/07MultiChannelTemplates.ipynb
@@ -1684,82 +1684,6 @@
     "Note that an exception will be raised during the sampling of the waveforms (i.e., during the sequencing process) if the subtemplates have different length."
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Instantionation duration check\n",
-    "By default the AtomicMultiChannelPulseTemplate checks whether the durations are equal on construction. It is possible to do this check during instantiation (when create_program is called) by providing the `duration` keyword argument. This can either be an expression or `True`. If it is an expression all subwaveforms have to have a duration equal to it. If it is `True` all waveforms have to have an unspecified equal duration."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 3,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Instantiation duration check:\n",
-      "Could not assert duration equality of duration_a and duration_b\n",
-      "\n",
-      "Instantionation duration check with no specified value:\n",
-      "The durations are not all equal. {'a': mpq(3,1), 'b': mpq(4,1)}\n",
-      "\n",
-      "Instantionation duration check with specified value\n",
-      "The duration does not equal the expected duration 4\n"
-     ]
-    }
-   ],
-   "source": [
-    "from qupulse.pulses import FunctionPT, AtomicMultiChannelPT\n",
-    "\n",
-    "template_a = FunctionPT('-sin(t)**2', 'duration_a', channel='a')\n",
-    "template_b = FunctionPT('-cos(t)**2', 'duration_b', channel='b')\n",
-    "\n",
-    "try:\n",
-    "    # instantiation duration check\n",
-    "    template = AtomicMultiChannelPT(\n",
-    "        template_a,\n",
-    "        template_b,\n",
-    "        identifier='3-channel-combined-template'\n",
-    "    )\n",
-    "except ValueError as err:\n",
-    "    print('Instantiation duration check:')\n",
-    "    print(err)\n",
-    "\n",
-    "# instantionation duration check with no specified value\n",
-    "template_unspecified = AtomicMultiChannelPT(\n",
-    "    template_a,\n",
-    "    template_b,\n",
-    "    duration=True\n",
-    ")\n",
-    "\n",
-    "template_unspecified.create_program(parameters=dict(duration_a=3, duration_b=3))\n",
-    "try:\n",
-    "    template_unspecified.create_program(parameters=dict(duration_a=3, duration_b=4))\n",
-    "except ValueError as err:\n",
-    "    print()\n",
-    "    print('Instantionation duration check with no specified value:')\n",
-    "    print(err.args[0], err.args[1])\n",
-    "\n",
-    "\n",
-    "# instantionation duration check with specified value\n",
-    "template_specified = AtomicMultiChannelPT(\n",
-    "    template_a,\n",
-    "    template_b,\n",
-    "    duration='my_duration'\n",
-    ")\n",
-    "template_specified.create_program(parameters=dict(duration_a=3, duration_b=3, my_duration=3))\n",
-    "try:\n",
-    "    template_specified.create_program(parameters=dict(duration_a=3, duration_b=3, my_duration=4))\n",
-    "except ValueError as err:\n",
-    "    print()\n",
-    "    print('Instantionation duration check with specified value')\n",
-    "    print(err.args[0], err.args[1])\n"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -1771,7 +1695,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
diff --git a/qupulse/_program/waveforms.py b/qupulse/_program/waveforms.py
index 102792e6..010827c6 100644
--- a/qupulse/_program/waveforms.py
+++ b/qupulse/_program/waveforms.py
@@ -12,7 +12,7 @@ from typing import Union, Set, Sequence, NamedTuple, Tuple, Any, Iterable, Froze
 import numpy as np
 
 from qupulse import ChannelID
-from qupulse.utils import checked_int_cast, isclose
+from qupulse.utils import checked_int_cast
 from qupulse.utils.types import TimeType, time_from_float
 from qupulse.comparable import Comparable
 from qupulse.expressions import ExpressionScalar
@@ -395,30 +395,17 @@ class MultiChannelWaveform(Waveform):
         self._sub_waveforms = tuple(sorted(flatten_sub_waveforms(sub_waveforms),
                                            key=get_sub_waveform_sort_key))
 
+        if not all(waveform.duration == self._sub_waveforms[0].duration for waveform in self._sub_waveforms[1:]):
+            raise ValueError(
+                "MultiChannelWaveform cannot be constructed from channel waveforms of different"
+                "lengths."
+            )
         self.__defined_channels = set()
         for waveform in self._sub_waveforms:
             if waveform.defined_channels & self.__defined_channels:
-                raise ValueError('Channel may not be defined in multiple waveforms',
-                                 waveform.defined_channels & self.__defined_channels)
+                raise ValueError('Channel may not be defined in multiple waveforms')
             self.__defined_channels |= waveform.defined_channels
 
-        if not all(isclose(waveform.duration, self._sub_waveforms[0].duration) for waveform in self._sub_waveforms[1:]):
-            # meaningful error message:
-            durations = {}
-
-            for waveform in self._sub_waveforms:
-                for duration, channels in durations.items():
-                    if isclose(waveform.duration, duration):
-                        channels.update(waveform.defined_channels)
-                        break
-                else:
-                    durations[waveform.duration] = set(waveform.defined_channels)
-
-            raise ValueError(
-                "MultiChannelWaveform cannot be constructed from channel waveforms of different durations.",
-                durations
-            )
-
     @property
     def duration(self) -> TimeType:
         return self._sub_waveforms[0].duration
diff --git a/qupulse/pulses/multi_channel_pulse_template.py b/qupulse/pulses/multi_channel_pulse_template.py
index 30988c28..a90a7cca 100644
--- a/qupulse/pulses/multi_channel_pulse_template.py
+++ b/qupulse/pulses/multi_channel_pulse_template.py
@@ -11,13 +11,14 @@ from typing import Dict, List, Optional, Any, Iterable, Union, Set, Sequence
 import numbers
 import warnings
 
+import numpy
+
+
 from qupulse.serialization import Serializer, PulseRegistryType
 
 from qupulse.pulses.conditions import Condition
-from qupulse.utils import isclose
-from qupulse.utils.sympy import almost_equal
 from qupulse.utils.types import ChannelID, TimeType
-from qupulse._program.waveforms import MultiChannelWaveform, Waveform
+from qupulse._program.waveforms import MultiChannelWaveform
 from qupulse.pulses.pulse_template import PulseTemplate, AtomicPulseTemplate
 from qupulse.pulses.mapping_pulse_template import MappingPulseTemplate, MappingTuple
 from qupulse.pulses.parameters import Parameter, ParameterConstrainer
@@ -28,29 +29,13 @@ __all__ = ["AtomicMultiChannelPulseTemplate"]
 
 
 class AtomicMultiChannelPulseTemplate(AtomicPulseTemplate, ParameterConstrainer):
-    """Combines multiple PulseTemplates that are defined on different channels into an AtomicPulseTemplate."""
     def __init__(self,
                  *subtemplates: Union[AtomicPulseTemplate, MappingTuple, MappingPulseTemplate],
                  external_parameters: Optional[Set[str]]=None,
                  identifier: Optional[str]=None,
                  parameter_constraints: Optional[List]=None,
                  measurements: Optional[List[MeasurementDeclaration]]=None,
-                 registry: PulseRegistryType=None,
-                 duration: Union[str, Expression, bool]=False) -> None:
-        """Parallels multiple AtomicPulseTemplates of the same duration. The duration equality check is performed on
-        construction by default. If the duration keyword argument is given the check is performed on instantiation
-        (when build_waveform is called). duration can be a Expression to enforce a certain duration or True for an
-        unspecified duration.
-
-        Args:
-            *subtemplates: Positional arguments are subtemplates to combine.
-            identifier: Forwarded to AtomicPulseTemplate.__init__
-            parameter_constraints: Forwarded to ParameterConstrainer.__init__
-            measurements: Forwarded to AtomicPulseTemplate.__init__
-            duration: Enforced duration of the pulse template on instantiation. build_waveform checks all sub-waveforms
-            have this duration. If True the equality of durations is only checked durtin instantiation not construction.
-            external_parameters: No functionality. (Deprecated)
-        """
+                 registry: PulseRegistryType=None) -> None:
         AtomicPulseTemplate.__init__(self, identifier=identifier, measurements=measurements)
         ParameterConstrainer.__init__(self, parameter_constraints=parameter_constraints)
 
@@ -85,38 +70,28 @@ class AtomicMultiChannelPulseTemplate(AtomicPulseTemplate, ParameterConstrainer)
             warnings.warn("external_parameters is an obsolete argument and will be removed in the future.",
                           category=DeprecationWarning)
 
-        if not duration:
-            duration = self._subtemplates[0].duration
-            for subtemplate in self._subtemplates[1:]:
-                if almost_equal(duration.sympified_expression, subtemplate.duration.sympified_expression):
-                    continue
-                else:
-                    raise ValueError('Could not assert duration equality of {} and {}'.format(duration,
-                                                                                              subtemplate.duration))
-            self._duration = None
-        elif duration is True:
-            self._duration = None
-        else:
-            self._duration = ExpressionScalar(duration)
+        duration = self._subtemplates[0].duration
+        for subtemplate in self._subtemplates[1:]:
+            if (duration == subtemplate.duration) is True:
+                continue
+            else:
+                raise ValueError('Could not assert duration equality of {} and {}'.format(duration,
+                                                                                          subtemplate.duration))
 
         self._register(registry=registry)
 
     @property
-    def duration(self) -> ExpressionScalar:
-        if self._duration:
-            return self._duration
-        else:
-            return self._subtemplates[0].duration
+    def duration(self) -> Expression:
+        return self._subtemplates[0].duration
 
     @property
     def parameter_names(self) -> Set[str]:
         return set.union(self.measurement_parameters,
                          self.constrained_parameters,
-                         *(st.parameter_names for st in self._subtemplates),
-                         self._duration.variables if self._duration else ())
+                         *(st.parameter_names for st in self._subtemplates))
 
     @property
-    def subtemplates(self) -> Sequence[Union[AtomicPulseTemplate, MappingPulseTemplate]]:
+    def subtemplates(self) -> Sequence[AtomicPulseTemplate]:
         return self._subtemplates
 
     @property
@@ -128,7 +103,7 @@ class AtomicMultiChannelPulseTemplate(AtomicPulseTemplate, ParameterConstrainer)
         return super().measurement_names.union(*(st.measurement_names for st in self._subtemplates))
 
     def build_waveform(self, parameters: Dict[str, numbers.Real],
-                       channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Waveform]:
+                       channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional['MultiChannelWaveform']:
         self.validate_parameter_constraints(parameters=parameters)
 
         sub_waveforms = []
@@ -140,21 +115,10 @@ class AtomicMultiChannelPulseTemplate(AtomicPulseTemplate, ParameterConstrainer)
 
         if len(sub_waveforms) == 0:
             return None
-
         if len(sub_waveforms) == 1:
-            waveform = sub_waveforms[0]
+            return sub_waveforms[0]
         else:
-            waveform = MultiChannelWaveform(sub_waveforms)
-
-        if self._duration:
-            expected_duration = self._duration.evaluate_numeric(**parameters)
-
-            if not isclose(expected_duration, waveform.duration):
-                raise ValueError('The duration does not '
-                                 'equal the expected duration',
-                                 expected_duration, waveform.duration)
-
-        return waveform
+            return MultiChannelWaveform(sub_waveforms)
 
     def get_measurement_windows(self,
                                 parameters: Dict[str, numbers.Real],
diff --git a/qupulse/utils/__init__.py b/qupulse/utils/__init__.py
index 6f3ad0ce..1abd16b7 100644
--- a/qupulse/utils/__init__.py
+++ b/qupulse/utils/__init__.py
@@ -2,13 +2,7 @@ from typing import Union
 
 import numpy
 
-try:
-    from math import isclose
-except ImportError:
-    # py version < 3.5
-    isclose = None
-
-__all__ = ["checked_int_cast", "is_integer", "isclose"]
+__all__ = ["checked_int_cast", "is_integer"]
 
 
 def checked_int_cast(x: Union[float, int, numpy.ndarray], epsilon: float=1e-6) -> int:
@@ -25,13 +19,3 @@ def checked_int_cast(x: Union[float, int, numpy.ndarray], epsilon: float=1e-6) -
 
 def is_integer(x: Union[float, int], epsilon: float=1e-6) -> bool:
     return abs(x - int(round(x))) < epsilon
-
-
-def _fallback_is_close(a, b, *, rel_tol=1e-09, abs_tol=0.0):
-    """Copied from https://docs.python.org/3/library/math.html
-    Does no error checks."""
-    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)  # pragma: no cover
-
-
-if not isclose:
-    isclose = _fallback_is_close
diff --git a/qupulse/utils/sympy.py b/qupulse/utils/sympy.py
index e4592332..70956224 100644
--- a/qupulse/utils/sympy.py
+++ b/qupulse/utils/sympy.py
@@ -1,6 +1,7 @@
 from typing import Union, Dict, Tuple, Any, Sequence, Optional
 from numbers import Number
 from types import CodeType
+import warnings
 
 import builtins
 import math
@@ -8,6 +9,15 @@ import math
 import sympy
 import numpy
 
+try:
+    import scipy.special as _special_functions
+except ImportError:
+    _special_functions = {fname: numpy.vectorize(fobject)
+                          for fname, fobject in math.__dict__.items()
+                          if not fname.startswith('_') and fname not in numpy.__dict__}
+    warnings.warn('scipy is not installed. This reduces the set of available functions to those present in numpy + '
+                  'manually vectorized functions in math.')
+
 
 __all__ = ["sympify", "substitute_with_eval", "to_numpy", "get_variables", "get_free_symbols", "recursive_substitution",
            "evaluate_lambdified", "get_most_simple_representation"]
@@ -189,6 +199,8 @@ _math_environment = {**_base_environment, **math.__dict__}
 _numpy_environment = {**_base_environment, **numpy.__dict__}
 _sympy_environment = {**_base_environment, **sympy.__dict__}
 
+_lambdify_modules = [{'ceiling': numpy_compatible_ceiling}, 'numpy', _special_functions]
+
 
 def evaluate_compiled(expression: sympy.Expr,
              parameters: Dict[str, Union[numpy.ndarray, Number]],
@@ -211,20 +223,6 @@ def evaluate_lambdified(expression: Union[sympy.Expr, numpy.ndarray],
                         variables: Sequence[str],
                         parameters: Dict[str, Union[numpy.ndarray, Number]],
                         lambdified) -> Tuple[Any, Any]:
-    lambdified = lambdified or sympy.lambdify(variables, expression,
-                                              [{'ceiling': numpy_compatible_ceiling}, 'numpy'])
+    lambdified = lambdified or sympy.lambdify(variables, expression, _lambdify_modules)
 
     return lambdified(**parameters), lambdified
-
-
-def almost_equal(lhs: sympy.Expr, rhs: sympy.Expr, epsilon: float=1e-15) -> Optional[bool]:
-    """Returns True (or False) if the two expressions are almost equal (or not). Returns None if this cannot be
-    determined."""
-    relation = sympy.simplify(sympy.Abs(lhs - rhs) <= epsilon)
-
-    if relation is sympy.true:
-        return True
-    elif relation is sympy.false:
-        return False
-    else:
-        return None
diff --git a/requirements.txt b/requirements.txt
index 78a3ca6d..08762bcf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,6 +5,7 @@ PyVISA==1.9.1
 sympy==1.3
 gmpy2==2.0.8
 
+certifi==2018.8.24
 cycler==0.10.0
 kiwisolver==1.0.1
 mpmath==1.0.0

utils.sympy.evaluate_lambdified fails if erfc is in expression
We currently only use `numpy` for lambdification which does not have the erfc function.

The user probably expects that functions from sympy can be used in such a case.

Two options:
 - vectorize functions from `math`
 - use `scipy.special`
diff --git a/tests/expression_tests.py b/tests/expression_tests.py
index c2981c07..c6097a1d 100644
--- a/tests/expression_tests.py
+++ b/tests/expression_tests.py
@@ -348,6 +348,14 @@ class ExpressionScalarTests(unittest.TestCase):
 
         self.assertFalse(ExpressionScalar(456).is_nan())
 
+    def test_special_function_numeric_evaluation(self):
+        expr = Expression('erfc(t)')
+        data = [-1., 0., 1.]
+        expected = np.array([1.84270079, 1., 0.15729921])
+        result = expr.evaluate_numeric(t=data)
+
+        np.testing.assert_allclose(expected, result)
+
 
 class ExpressionExceptionTests(unittest.TestCase):
     def test_expression_variable_missing(self):
diff --git a/tests/pulses/multi_channel_pulse_template_tests.py b/tests/pulses/multi_channel_pulse_template_tests.py
index cffe2bb9..dd476d62 100644
--- a/tests/pulses/multi_channel_pulse_template_tests.py
+++ b/tests/pulses/multi_channel_pulse_template_tests.py
@@ -65,49 +65,6 @@ class AtomicMultiChannelPulseTemplateTest(unittest.TestCase):
         with self.assertRaises(TypeError):
             AtomicMultiChannelPulseTemplate((non_atomic_pt, {'A': 'C'}), atomic_pt)
 
-    def test_instantiation_duration_check(self):
-        subtemplates = [DummyPulseTemplate(parameter_names={'p1'},
-                                           measurement_names={'m1'},
-                                           defined_channels={'c1'},
-                                           duration='t_1',
-                                           waveform=DummyWaveform(duration=3, defined_channels={'c1'})),
-                        DummyPulseTemplate(parameter_names={'p2'},
-                                           measurement_names={'m2'},
-                                           defined_channels={'c2'},
-                                           duration='t_2',
-                                           waveform=DummyWaveform(duration=3, defined_channels={'c2'})),
-                        DummyPulseTemplate(parameter_names={'p3'},
-                                           measurement_names={'m3'},
-                                           defined_channels={'c3'},
-                                           duration='t_3',
-                                           waveform=DummyWaveform(duration=4, defined_channels={'c3'}))]
-
-        with self.assertRaisesRegex(ValueError, 'duration equality'):
-            AtomicMultiChannelPulseTemplate(*subtemplates)
-
-        amcpt = AtomicMultiChannelPulseTemplate(*subtemplates, duration=True)
-        self.assertIs(amcpt.duration, subtemplates[0].duration)
-
-        with self.assertRaisesRegex(ValueError, 'duration'):
-            amcpt.build_waveform(parameters=dict(t_1=3, t_2=3, t_3=3),
-                                 channel_mapping={ch: ch for ch in 'c1 c2 c3'.split()})
-
-        subtemplates[2].waveform = None
-        amcpt.build_waveform(parameters=dict(t_1=3, t_2=3, t_3=3),
-                             channel_mapping={ch: ch for ch in 'c1 c2 c3'.split()})
-
-        amcpt = AtomicMultiChannelPulseTemplate(*subtemplates, duration='t_0')
-        with self.assertRaisesRegex(ValueError, 'duration'):
-            amcpt.build_waveform(parameters=dict(t_1=3, t_2=3, t_3=3, t_0=4),
-                                 channel_mapping={ch: ch for ch in 'c1 c2 c3'.split()})
-        with self.assertRaisesRegex(ValueError, 'duration'):
-            amcpt.build_waveform(parameters=dict(t_1=3+1e-9, t_2=3, t_3=3, t_0=4),
-                                 channel_mapping={ch: ch for ch in 'c1 c2 c3'.split()})
-        amcpt.build_waveform(parameters=dict(t_1=3, t_2=3, t_3=3, t_0=3),
-                             channel_mapping={ch: ch for ch in 'c1 c2 c3'.split()})
-        amcpt.build_waveform(parameters=dict(t_1=3+1e-11, t_2=3, t_3=3, t_0=3),
-                             channel_mapping={ch: ch for ch in 'c1 c2 c3'.split()})
-
     def test_external_parameters_warning(self):
         with self.assertWarnsRegex(DeprecationWarning, "external_parameters",
                                    msg="AtomicMultiChannelPulseTemplate did not issue a warning for argument external_parameters"):
@@ -126,6 +83,11 @@ class AtomicMultiChannelPulseTemplateTest(unittest.TestCase):
 
         self.assertEqual(template.duration, 't1')
 
+    def test_parameter_names(self) -> None:
+        template = AtomicMultiChannelPulseTemplate(*zip(self.subtemplates, self.param_maps, self.chan_maps),
+                                                   parameter_constraints={'pp1 > hugo'}, measurements={('meas', 'd', 1)})
+        self.assertEqual({'pp1', 'pp2', 'pp3', 'hugo', 'd'}, template.parameter_names)
+
     def test_mapping_template_pure_conversion(self):
         template = AtomicMultiChannelPulseTemplate(*zip(self.subtemplates, self.param_maps, self.chan_maps))
 
@@ -173,12 +135,6 @@ class AtomicMultiChannelPulseTemplateTest(unittest.TestCase):
         self.assertEqual(pt.parameter_names,
                          {'a', 'b', 'c', 'd', 'e'})
 
-    def test_parameter_names_2(self):
-        template = AtomicMultiChannelPulseTemplate(*zip(self.subtemplates, self.param_maps, self.chan_maps),
-                                                   parameter_constraints={'pp1 > hugo'},
-                                                   measurements={('meas', 'd', 1)},
-                                                   duration='my_duration')
-        self.assertEqual({'pp1', 'pp2', 'pp3', 'hugo', 'd', 'my_duration'}, template.parameter_names)
 
     def test_integral(self) -> None:
         sts = [DummyPulseTemplate(duration='t1', defined_channels={'A'},
diff --git a/tests/utils/sympy_tests.py b/tests/utils/sympy_tests.py
index 42f0eb5c..effee275 100644
--- a/tests/utils/sympy_tests.py
+++ b/tests/utils/sympy_tests.py
@@ -14,8 +14,7 @@ a_ = IndexedBase(a)
 b_ = IndexedBase(b)
 
 from qupulse.utils.sympy import sympify as qc_sympify, substitute_with_eval, recursive_substitution, Len,\
-    evaluate_lambdified, evaluate_compiled, get_most_simple_representation, get_variables, get_free_symbols,\
-    almost_equal
+    evaluate_lambdified, evaluate_compiled, get_most_simple_representation, get_variables, get_free_symbols
 
 
 ################################################### SUBSTITUTION #######################################################
@@ -310,13 +309,4 @@ class RepresentationTest(unittest.TestCase):
 
         st = get_most_simple_representation(qc_sympify('a + b'))
         self.assertIsInstance(st, str)
-        self.assertEqual(st, 'a + b')
-
-
-class AlmostEqualTests(unittest.TestCase):
-    def test_almost_equal(self):
-        self.assertTrue(almost_equal(sympy.sin(a) * 0.5, sympy.sin(a) / 2))
-        self.assertIsNone(almost_equal(sympy.sin(a) * 0.5, sympy.sin(b) / 2))
-        self.assertFalse(almost_equal(sympy.sin(a), sympy.sin(a) + 1e-14))
-
-        self.assertTrue(almost_equal(sympy.sin(a), sympy.sin(a) + 1e-14, epsilon=1e-13))
+        self.assertEqual(st, 'a + b')
\ No newline at end of file
diff --git a/tests/utils/utils_tests.py b/tests/utils/utils_tests.py
index c7b4dc8e..a3251a23 100644
--- a/tests/utils/utils_tests.py
+++ b/tests/utils/utils_tests.py
@@ -1,5 +1,4 @@
 import unittest
-from unittest import mock
 from qupulse.utils import checked_int_cast
 
 
@@ -33,44 +32,3 @@ class CheckedIntCastTest(unittest.TestCase):
             checked_int_cast(6 + 1e-11, epsilon=1e-15)
 
 
-class IsCloseTest(unittest.TestCase):
-    def test_isclose_fallback(self):
-        import math
-        import importlib
-        import builtins
-        import qupulse.utils as qutils
-
-        def dummy_is_close():
-            pass
-
-        if hasattr(math, 'isclose'):
-            dummy_is_close = math.isclose
-
-        original_import = builtins.__import__
-
-        def mock_import_missing_isclose(name, globals=None, locals=None, fromlist=(), level=0):
-            if name == 'math' and 'isclose' in fromlist:
-                raise ImportError(name)
-            else:
-                return original_import(name, globals=globals, locals=locals, fromlist=fromlist, level=level)
-
-        def mock_import_exsiting_isclose(name, globals=None, locals=None, fromlist=(), level=0):
-            if name == 'math' and 'isclose' in fromlist:
-                if not hasattr(math, 'isclose'):
-                    math.isclose = dummy_is_close
-                return math
-            else:
-                return original_import(name, globals=globals, locals=locals, fromlist=fromlist, level=level)
-
-        with mock.patch('builtins.__import__', mock_import_missing_isclose):
-            reloaded_qutils = importlib.reload(qutils)
-            self.assertIs(reloaded_qutils.isclose, reloaded_qutils._fallback_is_close)
-
-        with mock.patch('builtins.__import__', mock_import_exsiting_isclose):
-            reloaded_qutils = importlib.reload(qutils)
-            self.assertIs(reloaded_qutils.isclose, math.isclose)
-
-        if math.isclose is dummy_is_close:
-            # cleanup
-            delattr(math, 'isclose')
-

pip3 install .[plotting,zurich-instruments,tektronix,Faster-fractions]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y libgmp-dev libmpfr-dev libmpc-dev
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
cached-property==1.5.2
certifi==2021.5.30
coverage==6.2
cycler==0.11.0
execnet==1.9.0
gmpy2==2.1.5
importlib-metadata==4.8.3
iniconfig==1.1.1
kiwisolver==1.3.1
matplotlib==3.3.4
mpmath==1.3.0
numpy==1.19.5
packaging==21.3
Pillow==8.4.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.9.0.post0
qupulse @ file:///qupulse
six==1.17.0
sympy==1.9
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: qupulse
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cached-property==1.5.2
      - coverage==6.2
      - cycler==0.11.0
      - execnet==1.9.0
      - gmpy2==2.1.5
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - kiwisolver==1.3.1
      - matplotlib==3.3.4
      - mpmath==1.3.0
      - numpy==1.19.5
      - packaging==21.3
      - pillow==8.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.9.0.post0
      - qupulse==0.2
      - six==1.17.0
      - sympy==1.9
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/qupulse

tests/expression_tests.py::ExpressionScalarTests::test_special_function_numeric_evaluation
tests/expression_tests.py::ExpressionScalarTests::test_indexing tests/expression_tests.py::ExpressionScalarTests::test_partial_evaluation_vectorized tests/expression_tests.py::ExpressionScalarTests::test_variables_indexed tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_parameter_names tests/pulses/multi_channel_pulse_template_tests.py::MultiChannelPulseTemplateSequencingTests::test_build_sequence tests/pulses/multi_channel_pulse_template_tests.py::MultiChannelPulseTemplateSequencingTests::test_build_waveform tests/pulses/multi_channel_pulse_template_tests.py::MultiChannelPulseTemplateSequencingTests::test_build_waveform_none tests/pulses/multi_channel_pulse_template_tests.py::MultiChannelPulseTemplateSequencingTests::test_get_measurement_windows tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_conversion tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_deserialization tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_duplication_error tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_get_type_identifier tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_identifier tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_manual_garbage_collect tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_no_registration_before_correct_serialization tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_renamed tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_renamed_of_anonymous tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_serialization tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateSerializationTests::test_serialization_and_deserialization tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateOldSerializationTests::test_deserialize_old tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateOldSerializationTests::test_serialize_old tests/utils/sympy_tests.py::SympifyWrapperTests::test_index_sympify
tests/expression_tests.py::ExpressionTests::test_make tests/expression_tests.py::ExpressionVectorTests::test_eq tests/expression_tests.py::ExpressionVectorTests::test_evaluate_numeric tests/expression_tests.py::ExpressionVectorTests::test_evaluate_numeric_2d tests/expression_tests.py::ExpressionVectorTests::test_numeric_expression tests/expression_tests.py::ExpressionVectorTests::test_partial_evaluation tests/expression_tests.py::ExpressionVectorTests::test_symbolic_evaluation tests/expression_tests.py::ExpressionScalarTests::test_defined_comparison tests/expression_tests.py::ExpressionScalarTests::test_evaluate_numeric tests/expression_tests.py::ExpressionScalarTests::test_evaluate_numeric_without_numpy tests/expression_tests.py::ExpressionScalarTests::test_evaluate_numpy tests/expression_tests.py::ExpressionScalarTests::test_evaluate_symbolic tests/expression_tests.py::ExpressionScalarTests::test_evaluate_variable_missing tests/expression_tests.py::ExpressionScalarTests::test_hash tests/expression_tests.py::ExpressionScalarTests::test_is_nan tests/expression_tests.py::ExpressionScalarTests::test_number_comparison tests/expression_tests.py::ExpressionScalarTests::test_number_math tests/expression_tests.py::ExpressionScalarTests::test_original_expression tests/expression_tests.py::ExpressionScalarTests::test_partial_evaluation tests/expression_tests.py::ExpressionScalarTests::test_repr tests/expression_tests.py::ExpressionScalarTests::test_str tests/expression_tests.py::ExpressionScalarTests::test_symbolic_math tests/expression_tests.py::ExpressionScalarTests::test_sympy_math tests/expression_tests.py::ExpressionScalarTests::test_undefined_comparison tests/expression_tests.py::ExpressionScalarTests::test_variables tests/expression_tests.py::ExpressionExceptionTests::test_expression_variable_missing tests/expression_tests.py::ExpressionExceptionTests::test_non_numeric_evaluation tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_channel_intersection tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_defined_channels tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_duration tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_external_parameters_warning tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_init_empty tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_integral tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_mapping_template_mixed_conversion tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_mapping_template_pure_conversion tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_measurement_names tests/pulses/multi_channel_pulse_template_tests.py::AtomicMultiChannelPulseTemplateTest::test_non_atomic_subtemplates tests/pulses/multi_channel_pulse_template_tests.py::MultiChannelPulseTemplateSequencingTests::test_requires_stop tests/utils/sympy_tests.py::SympifyTests::test_complex_sympify tests/utils/sympy_tests.py::SympifyTests::test_index_sympify tests/utils/sympy_tests.py::SympifyTests::test_len_sympify tests/utils/sympy_tests.py::SympifyTests::test_simple_sympify tests/utils/sympy_tests.py::SympifyWrapperTests::test_complex_sympify tests/utils/sympy_tests.py::SympifyWrapperTests::test_len_sympify tests/utils/sympy_tests.py::SympifyWrapperTests::test_simple_sympify tests/utils/sympy_tests.py::SubstitutionTests::test_elem_func_substitution_cases tests/utils/sympy_tests.py::SubstitutionTests::test_simple_substitution_cases tests/utils/sympy_tests.py::SubstitutionTests::test_sum_substitution_cases tests/utils/sympy_tests.py::SubstituteWithEvalTests::test_elem_func_substitution_cases tests/utils/sympy_tests.py::SubstituteWithEvalTests::test_indexed_substitution_cases tests/utils/sympy_tests.py::SubstituteWithEvalTests::test_simple_substitution_cases tests/utils/sympy_tests.py::SubstituteWithEvalTests::test_vector_valued_cases tests/utils/sympy_tests.py::RecursiveSubstitutionTests::test_elem_func_substitution_cases tests/utils/sympy_tests.py::RecursiveSubstitutionTests::test_full_featured_cases tests/utils/sympy_tests.py::RecursiveSubstitutionTests::test_indexed_substitution_cases tests/utils/sympy_tests.py::RecursiveSubstitutionTests::test_simple_substitution_cases tests/utils/sympy_tests.py::RecursiveSubstitutionTests::test_sum_substitution_cases tests/utils/sympy_tests.py::RecursiveSubstitutionTests::test_vector_valued_cases tests/utils/sympy_tests.py::GetFreeSymbolsTests::test_get_free_symbols tests/utils/sympy_tests.py::GetFreeSymbolsTests::test_get_free_symbols_indexed tests/utils/sympy_tests.py::GetFreeSymbolsTests::test_get_variables tests/utils/sympy_tests.py::GetFreeSymbolsTests::test_get_variables_indexed tests/utils/sympy_tests.py::EvaluationTests::test_eval_array_expression tests/utils/sympy_tests.py::EvaluationTests::test_eval_array_values tests/utils/sympy_tests.py::EvaluationTests::test_eval_many_arguments tests/utils/sympy_tests.py::EvaluationTests::test_eval_simple tests/utils/sympy_tests.py::EvaluationTests::test_eval_simple_functions tests/utils/sympy_tests.py::EvaluationTests::test_eval_sum tests/utils/sympy_tests.py::CompiledEvaluationTest::test_eval_array_expression tests/utils/sympy_tests.py::CompiledEvaluationTest::test_eval_array_values tests/utils/sympy_tests.py::CompiledEvaluationTest::test_eval_many_arguments tests/utils/sympy_tests.py::CompiledEvaluationTest::test_eval_simple tests/utils/sympy_tests.py::CompiledEvaluationTest::test_eval_simple_functions tests/utils/sympy_tests.py::CompiledEvaluationTest::test_eval_sum tests/utils/sympy_tests.py::RepresentationTest::test_get_most_simple_representation tests/utils/utils_tests.py::CheckedIntCastTest::test_float_cast tests/utils/utils_tests.py::CheckedIntCastTest::test_int_forwarding tests/utils/utils_tests.py::CheckedIntCastTest::test_no_int_detection tests/utils/utils_tests.py::CheckedIntCastTest::test_variable_epsilon
requirements.txt doc/source/examples/07MultiChannelTemplates.ipynb ReleaseNotes.txt qupulse/utils/sympy.py qupulse/pulses/multi_channel_pulse_template.py qupulse/_program/waveforms.py qupulse/utils/__init__.py
requirements.txt doc/source/examples/07MultiChannelTemplates.ipynb ReleaseNotes.txt qupulse/utils/sympy.py qupulse/pulses/multi_channel_pulse_template.py qupulse/_program/waveforms.py qupulse/utils/__init__.py