def _run(self, duration, log, log_interval, progress, msg):
     # Simulation times
     if duration < 0:
         raise Exception('Simulation time can\'t be negative.')
     tmin = self._time
     tmax = tmin + duration
     # Gather global variables in model
     g = [self._model.time().qname()]
     # Parse log argument
     log = myokit.prepare_log(
         log,
         self._model,
         dims=(self._ncells, ),
         global_vars=g,
         if_empty=myokit.LOG_STATE + myokit.LOG_BOUND,
         allowed_classes=myokit.LOG_STATE + myokit.LOG_BOUND +
         myokit.LOG_INTER,
     )
     # Get event tuples
     # Logging period
     log_interval = 0 if log_interval is None else float(log_interval)
     # Get progress indication function (if any)
     if progress is None:
         progress = myokit._Simulation_progress
     if progress:
         if not isinstance(progress, myokit.ProgressReporter):
             raise ValueError(
                 'The argument "progress" must be either a'
                 ' subclass of myokit.ProgressReporter or None.')
     # Run simulation
     if duration > 0:
         # Initialize
         state_in = self._state
         state_out = list(state_in)
         self._sim.sim_init(self._ncells, self._conductance, tmin, tmax,
                            self._step_size, state_in,
                            state_out, self._protocol,
                            min(self._npaced,
                                self._ncells), log, log_interval)
         t = tmin
         try:
             if progress:
                 # Loop with feedback
                 with progress.job(msg):
                     r = 1.0 / duration if duration != 0 else 1
                     while t < tmax:
                         t = self._sim.sim_step()
                         if not progress.update(min((t - tmin) * r, 1)):
                             raise myokit.SimulationCancelledError()
             else:
                 # Loop without feedback
                 while t < tmax:
                     t = self._sim.sim_step()
         finally:
             # Clean even after keyboardinterrupt or exception
             self._sim.sim_clean()
         # Update state
         self._state = state_out
     # Return log
     return log
Beispiel #2
0
    def run(
            self, duration, log=None, log_interval=1, progress=None,
            msg='Running PSimulation'):
        """
        Runs a simulation and returns the logged results. Running a simulation
        has the following effects:

        - The internal state is updated to the last state in the simulation.
        - The simulation's time variable is updated to reflect the time
          elapsed during the simulation.

        The number of time units to simulate can be set with ``duration``.

        The variables to log can be indicated using the ``log`` argument. There
        are several options for its value:

        - ``None`` (default), to log all states.
        - An integer flag or a combination of flags. Options:
          ``myokit.LOG_NONE``, ``myokit.LOG_STATE``, ``myokit.LOG_INTER``,
          ``myokit.LOG_BOUND``.
        - A list of qnames or variable objects
        - A :class:`myokit.DataLog` obtained from a previous simulation.
          In this case, the newly logged data will be appended to the existing
          log.

        For more details on the ``log`` argument, see the function
        :meth:`myokit.prepare_log`.

        The method returns a :class:`myokit.DataLog` and a 3d numpy
        array. In the returned array, the first axis represents the time,
        the second axis is a tracked variable y and the third is a parameter p
        such that the point ``(t, y, p)`` represents ``dy/dp`` at time ``t``.
        For example, if ``d`` is the array of derivatives, to get the
        derivative of variables ``0`` with respect to parameter 2, use
        ``d[:,0,2]``.

        A log entry is created every time *at least* ``log_interval`` time
        units have passed. If ``log_interval <= 0`` every step taken is logged.

        To obtain feedback on the simulation progress, an object implementing
        the :class:`myokit.ProgressReporter` interface can be passed in.
        passed in as ``progress``. An optional description of the current
        simulation to use in the ProgressReporter can be passed in as `msg`.
        """
        # Simulation times
        if duration < 0:
            raise ValueError('Simulation time can\'t be negative.')
        tmin = self._time
        tmax = tmin + duration

        # Number of states, state derivatives
        ms = len(self._state)
        mv = len(self._variables)
        mp = len(self._parameters)

        # Final state and final state-parameter-derivative output lists
        state = [0.] * ms
        state_ddp = [0.] * (ms * mp)

        # Parse log argument
        log = myokit.prepare_log(
            log,
            self._model,
            if_empty=myokit.LOG_STATE + myokit.LOG_BOUND,
            allowed_classes=myokit.LOG_STATE + myokit.LOG_BOUND
            + myokit.LOG_INTER,
        )

        # Logging period (0 = log at every step)
        log_interval = float(log_interval)
        if log_interval < 0:
            log_interval = 0

        # Create empty list for variable-parameter-derivative lists
        varab_ddp = []

        # Get progress indication function (if any)
        if progress is None:
            progress = myokit._Simulation_progress
        if progress:
            if not isinstance(progress, myokit.ProgressReporter):
                raise ValueError(
                    'The argument `progress` must be either a subclass of'
                    ' myokit.ProgressReporter or None.')

        # Run simulation
        if duration > 0:
            # Initialize
            self._sim.sim_init(
                tmin,
                tmax,
                self._dt,
                list(self._values),
                list(self._state),
                list(self._state_ddp),
                state,
                state_ddp,
                self._protocol,
                log,
                varab_ddp,
                log_interval,
            )
            t = tmin
            try:
                if progress:
                    # Loop with feedback
                    with progress.job(msg):
                        r = 1.0 / duration
                        while t < tmax:
                            t = self._sim.sim_step()
                            if not progress.update(min((t - tmin) * r, 1)):
                                raise myokit.SimulationCancelledError()
                else:
                    # Loop without feedback
                    # (But with repeated returns to Python to allow Ctrl-C etc)
                    while t < tmax:
                        t = self._sim.sim_step()
            finally:
                # Clean even after KeyboardInterrupt or other Exception
                self._sim.sim_clean()

            # Update internal state
            self._state = list(state)
            self._state_ddp = list(state_ddp)
            self._time += duration

            # Convert derivatives to numpy arrays
            varab_ddp = np.array([
                np.array(np.array(x).reshape(mv, mp), copy=True)
                for x in varab_ddp])

        # Return
        return log, varab_ddp
Beispiel #3
0
 def _run(self, duration, log, log_interval, apd_threshold, progress, msg):
     # Reset error state
     self._error_state = None
     # Simulation times
     if duration < 0:
         raise Exception('Simulation time can\'t be negative.')
     tmin = self._time
     tmax = tmin + duration
     # Parse log argument
     log = myokit.prepare_log(log, self._model, if_empty=myokit.LOG_ALL)
     # Logging period (0 = disabled)
     log_interval = 0 if log_interval is None else float(log_interval)
     if log_interval < 0:
         log_interval = 0
     # Threshold for APD measurement
     root_list = None
     root_threshold = 0
     if apd_threshold is not None:
         if self._apd_var is None:
             raise ValueError('Threshold given but Simulation object was'
                              ' created without apd_var argument.')
         else:
             root_list = []
             root_threshold = float(apd_threshold)
     # Get progress indication function (if any)
     if progress is None:
         progress = myokit._Simulation_progress
     if progress:
         if not isinstance(progress, myokit.ProgressReporter):
             raise ValueError(
                 'The argument "progress" must be either a'
                 ' subclass of myokit.ProgressReporter or None.')
     # Determine benchmarking mode, create time() function if needed
     if self._model.binding('realtime') is not None:
         import timeit
         bench = timeit.default_timer
     else:
         bench = None
     # Run simulation
     with myokit.SubCapture():
         arithmetic_error = None
         if duration > 0:
             # Initialize
             state = [0] * len(self._state)
             bound = [0, 0, 0, 0]  # time, pace, realtime, evaluations
             self._sim.sim_init(
                 tmin,
                 tmax,
                 list(self._state),
                 state,
                 bound,
                 self._protocol,
                 self._fixed_form_protocol,
                 log,
                 log_interval,
                 root_list,
                 root_threshold,
                 bench,
             )
             t = tmin
             try:
                 if progress:
                     # Loop with feedback
                     with progress.job(msg):
                         r = 1.0 / duration if duration != 0 else 1
                         while t < tmax:
                             t = self._sim.sim_step()
                             if not progress.update(min((t - tmin) * r, 1)):
                                 raise myokit.SimulationCancelledError()
                 else:
                     # Loop without feedback
                     while t < tmax:
                         t = self._sim.sim_step()
             except ArithmeticError as ea:
                 self._error_state = list(state)
                 txt = [
                     'A numerical error occurred during simulation at'
                     ' t = ' + str(t) + '.', 'Last reached state: '
                 ]
                 txt.extend([
                     '  ' + x
                     for x in self._model.format_state(state).splitlines()
                 ])
                 txt.append('Inputs for binding: ')
                 txt.append('  time        = ' + myokit.strfloat(bound[0]))
                 txt.append('  pace        = ' + myokit.strfloat(bound[1]))
                 txt.append('  realtime    = ' + myokit.strfloat(bound[2]))
                 txt.append('  evaluations = ' + myokit.strfloat(bound[3]))
                 try:
                     self._model.eval_state_derivatives(state)
                 except myokit.NumericalError as en:
                     txt.append(en.message)
                 raise myokit.SimulationError('\n'.join(txt))
             except Exception as e:
                 # Store error state
                 self._error_state = list(state)
                 # Check for zero-step error
                 if e.message[0:9] == 'ZERO_STEP':
                     time = float(e.message[10:])
                     raise myokit.SimulationError('Too many failed steps at'
                                                  ' t=' + str(time))
                 # Unhandled exception: re-raise!
                 raise
             finally:
                 # Clean even after KeyboardInterrupt or other Exception
                 self._sim.sim_clean()
             # Update internal state
             self._state = state
     # Return
     if root_list is not None:
         # Calculate apds and return (log, apds)
         st = []
         dr = []
         if root_list:
             roots = iter(root_list)
             time, direction = roots.next()
             tlast = time if direction > 0 else None
             for time, direction in roots:
                 if direction > 0:
                     tlast = time
                 else:
                     st.append(tlast)
                     dr.append(time - tlast)
         apds = myokit.DataLog()
         apds['start'] = st
         apds['duration'] = dr
         return log, apds
     else:
         # Return log
         return log
    def run(self, duration, log=None, log_interval=5, progress=None,
            msg='Running ICSimulation'):
        """
        Runs a simulation and returns the logged results. Running a simulation
        has the following effects:

        - The internal state is updated to the last state in the simulation.
        - The simulation's time variable is updated to reflect the time
          elapsed during the simulation.

        The number of time units to simulate can be set with ``duration``.

        The variables to log can be indicated using the ``log`` argument. There
        are several options for its value:

        - ``None`` (default), to log all states.
        - An integer flag or a combination of flags. Options:
          ``myokit.LOG_NONE``, ``myokit.LOG_STATE``, ``myokit.LOG_INTER``,
          ``myokit.LOG_BOUND``.
        - A list of qnames or variable objects
        - A :class:`myokit.DataLog` obtained from a previous simulation.
          In this case, the newly logged data will be appended to the existing
          log.

        For more details on the ``log`` argument, see the function
        :meth:`myokit.prepare_log`.

        The method returns a :class:`myokit.DataLog` and a 3d numpy
        array. In the returned array, the first axis represents the time,
        the second axis is a state x and the third is a state y such that the
        point ``(t, x, y)`` represents ``dx/dy(0)`` at time t. For example, if
        ``p`` is the array of derivatives, to get the derivative of state 0
        with respect to the initial value of state 1, use ``p[:,0,1]``.

        A log entry is created every time *at least* ``log_interval`` time
        units have passed.

        To obtain feedback on the simulation progress, an object implementing
        the :class:`myokit.ProgressReporter` interface can be passed in.
        passed in as ``progress``. An optional description of the current
        simulation to use in the ProgressReporter can be passed in as `msg`.
        """
        # Simulation times
        if duration < 0:
            raise ValueError('Simulation time can\'t be negative.')
        tmin = self._time
        tmax = tmin + duration

        # Parse log argument
        log = myokit.prepare_log(
            log,
            self._model,
            if_empty=myokit.LOG_STATE + myokit.LOG_BOUND,
            allowed_classes=myokit.LOG_STATE + myokit.LOG_BOUND
            + myokit.LOG_INTER,
        )

        # Logging period (0 = disabled)
        log_interval = float(log_interval)
        if log_interval < 0:
            log_interval = 0

        # Create empty list for derivative lists
        derivs = []

        # Get progress indication function (if any)
        if progress is None:
            progress = myokit._Simulation_progress
        if progress:
            if not isinstance(progress, myokit.ProgressReporter):
                raise ValueError(
                    'The argument "progress" must be either a subclass of'
                    ' myokit.ProgressReporter or None.')

        # Run simulation
        if duration > 0:
            # Initialize
            n = len(self._state)
            state = [0] * n
            deriv = [0] * (n ** 2)
            self._sim.sim_init(
                tmin,
                tmax,
                self._dt,
                list(self._state),
                list(self._deriv),
                state,
                deriv,
                self._protocol,
                log,
                derivs,
                log_interval,
            )
            t = tmin
            try:
                if progress:
                    # Loop with feedback
                    with progress.job(msg):
                        r = 1 / duration
                        while t < tmax:
                            t = self._sim.sim_step()
                            if not progress.update(min((t - tmin) * r, 1)):
                                raise myokit.SimulationCancelledError()
                else:
                    # Loop without feedback
                    while t < tmax:
                        t = self._sim.sim_step()
            finally:
                # Clean even after KeyboardInterrupt or other Exception
                self._sim.sim_clean()
            # Update internal state
            self._state = list(state)
            self._deriv = list(deriv)
            self._time += duration
            # Convert derivatives to numpy arrays
            # Using
            #   derivs = [np.array(x).reshape(n,n) for x in derivs]
            # will create a list of views of arrays
            # to avoid the overhead of views, perhaps it's better to copy this
            # view into a new array explicitly?
            derivs = np.array([
                np.array(np.array(x).reshape(n, n), copy=True) for x in derivs
            ])

        # Return
        return log, derivs
Beispiel #5
0
    def _run(self, duration, log, log_interval, log_times, apd_threshold,
             progress, msg):

        # Reset error state
        self._error_state = None

        # Simulation times
        if duration < 0:
            raise ValueError('Simulation time can\'t be negative.')
        tmin = self._time
        tmax = tmin + duration

        # Parse log argument
        log = myokit.prepare_log(log, self._model, if_empty=myokit.LOG_ALL)

        # Logging period (None or 0 = disabled)
        log_interval = 0 if log_interval is None else float(log_interval)
        if log_interval < 0:
            log_interval = 0

        # Logging points (None or empty list = disabled)
        if log_times is not None:
            log_times = [float(x) for x in log_times]
            if len(log_times) == 0:
                log_times = None
            else:
                # Allow duplicates, but always non-decreasing!
                import numpy as np
                x = np.asarray(log_times)
                if np.any(x[1:] < x[:-1]):
                    raise ValueError(
                        'Values in log_times must be non-decreasing.')
                del (x, np)
        if log_times is not None and log_interval > 0:
            raise ValueError(
                'The arguments log_times and log_interval cannot be used'
                ' simultaneously.')

        # Threshold for APD measurement
        root_list = None
        root_threshold = 0
        if apd_threshold is not None:
            if self._apd_var is None:
                raise ValueError('Threshold given but Simulation object was'
                                 ' created without apd_var argument.')
            else:
                root_list = []
                root_threshold = float(apd_threshold)

        # Get progress indication function (if any)
        if progress is None:
            progress = myokit._Simulation_progress
        if progress:
            if not isinstance(progress, myokit.ProgressReporter):
                raise ValueError(
                    'The argument "progress" must be either a'
                    ' subclass of myokit.ProgressReporter or None.')

        # Determine benchmarking mode, create time() function if needed
        if self._model.binding('realtime') is not None:
            import timeit
            bench = timeit.default_timer
        else:
            bench = None

        # Run simulation
        # The simulation is run only if (tmin + duration > tmin). This is a
        # stronger check than (duration == 0), which will return true even for
        # very short durations (and will cause zero iterations of the
        # "while (t < tmax)" loop below).
        istate = list(self._state)
        if tmin + duration > tmin:

            # Lists to return state in
            rstate = list(istate)
            rbound = [0, 0, 0, 0]  # time, pace, realtime, evaluations

            # Initialize
            self._sim.sim_init(
                tmin,
                tmax,
                istate,
                rstate,
                rbound,
                self._protocol,
                self._fixed_form_protocol,
                log,
                log_interval,
                log_times,
                root_list,
                root_threshold,
                bench,
            )
            t = tmin

            try:
                if progress:
                    # Loop with feedback
                    with progress.job(msg):
                        r = 1.0 / duration if duration != 0 else 1
                        while t < tmax:
                            t = self._sim.sim_step()
                            if not progress.update(min((t - tmin) * r, 1)):
                                raise myokit.SimulationCancelledError()
                else:
                    # Loop without feedback
                    while t < tmax:
                        t = self._sim.sim_step()
            except ArithmeticError as e:
                # Some CVODE errors are set to raise an ArithmeticError,
                # which users may be able to debug.
                self._error_state = list(rstate)
                txt = [
                    'A numerical error occurred during simulation at'
                    ' t = ' + str(t) + '.', 'Last reached state: '
                ]
                txt.extend([
                    '  ' + x
                    for x in self._model.format_state(rstate).splitlines()
                ])
                txt.append('Inputs for binding: ')
                txt.append('  time        = ' + myokit.float.str(rbound[0]))
                txt.append('  pace        = ' + myokit.float.str(rbound[1]))
                txt.append('  realtime    = ' + myokit.float.str(rbound[2]))
                txt.append('  evaluations = ' + myokit.float.str(rbound[3]))
                txt.append(str(e))
                try:
                    self._model.evaluate_derivatives(rstate)
                except myokit.NumericalError as en:
                    txt.append(str(en))
                raise myokit.SimulationError('\n'.join(txt))
            except Exception as e:

                # Store error state
                self._error_state = list(rstate)

                # Check for known CVODE errors
                if 'Function CVode()' in str(e):
                    raise myokit.SimulationError(str(e))

                # Check for zero step error
                if str(e)[:10] == 'ZERO_STEP ':  # pragma: no cover
                    t = float(str(e)[10:])
                    raise myokit.SimulationError(
                        'Maximum number of zero-size steps made at t=' +
                        str(t))

                # Unknown exception: re-raise!
                raise
            finally:
                # Clean even after KeyboardInterrupt or other Exception
                self._sim.sim_clean()

            # Update internal state
            self._state = rstate

        # Return
        if root_list is not None:
            # Calculate apds and return (log, apds)
            st = []
            dr = []
            if root_list:
                roots = iter(root_list)
                time, direction = next(roots)
                tlast = time if direction > 0 else None
                for time, direction in roots:
                    if direction > 0:
                        tlast = time
                    else:
                        st.append(tlast)
                        dr.append(time - tlast)
            apds = myokit.DataLog()
            apds['start'] = st
            apds['duration'] = dr
            return log, apds
        else:
            # Return log
            return log
Beispiel #6
0
    def _run(self, duration, logf, logt, log_interval, report_nan, progress,
             msg):
        # Simulation times
        if duration < 0:
            raise Exception('Simulation duration can\'t be negative.')
        tmin = self._time
        tmax = tmin + duration

        # Gather global variables in fiber model
        gf = []
        gt = []
        for label in self._global:
            v = self._modelf.binding(label)
            if v is not None:
                gf.append(v.qname())
            v = self._modelt.binding(label)
            if v is not None:
                gt.append(v.qname())

        # Parse logf argument
        logf = myokit.prepare_log(logf,
                                  self._modelf,
                                  dims=self._ncellsf,
                                  global_vars=gf,
                                  if_empty=myokit.LOG_STATE + myokit.LOG_BOUND,
                                  allowed_classes=myokit.LOG_STATE +
                                  myokit.LOG_BOUND + myokit.LOG_INTER,
                                  precision=self._precision)

        # Parse logt argument
        logt = myokit.prepare_log(logt,
                                  self._modelt,
                                  dims=self._ncellst,
                                  global_vars=gt,
                                  if_empty=myokit.LOG_STATE + myokit.LOG_BOUND,
                                  allowed_classes=myokit.LOG_STATE +
                                  myokit.LOG_BOUND + myokit.LOG_INTER,
                                  precision=self._precision)

        # Create list of intermediary fiber variables that need to be logged
        inter_logf = []
        vars_checked = set()
        for var in logf.keys():
            var = myokit.split_key(var)[1]
            if var in vars_checked:
                continue
            vars_checked.add(var)
            var = self._modelf.get(var)
            if var.is_intermediary() and not var.is_bound():
                inter_logf.append(var)

        # Create list of intermediary tissue variables that need to be logged
        inter_logt = []
        vars_checked = set()
        for var in logt.keys():
            var = myokit.split_key(var)[1]
            if var in vars_checked:
                continue
            vars_checked.add(var)
            var = self._modelt.get(var)
            if var.is_intermediary() and not var.is_bound():
                inter_logt.append(var)

        # Get preferred platform/device combo from configuration file
        platform, device = myokit.OpenCL.load_selection_bytes()

        # Generate kernels
        kernel_file = os.path.join(myokit.DIR_CFUNC, KERNEL_FILE)
        args = {
            'precision': self._precision,
            'native_math': self._native_math,
            'diffusion': True,
            'fields': [],
            'rl_states': {},
        }
        args['model'] = self._modelf
        args['vmvar'] = self._vmf
        args['bound_variables'] = self._bound_variablesf
        args['inter_log'] = inter_logf
        args['paced_cells'] = self._paced_cells
        if myokit.DEBUG:
            print('-' * 79)
            print(
                self._code(kernel_file,
                           args,
                           line_numbers=myokit.DEBUG_LINE_NUMBERS))
        else:
            kernelf = self._export(kernel_file, args)
        args['model'] = self._modelt
        args['vmvar'] = self._vmt
        args['bound_variables'] = self._bound_variablest
        args['inter_log'] = inter_logt
        args['paced_cells'] = []
        if myokit.DEBUG:
            print('-' * 79)
            print(
                self._code(kernel_file,
                           args,
                           line_numbers=myokit.DEBUG_LINE_NUMBERS))
            import sys
            sys.exit(1)
        else:
            kernelt = self._export(kernel_file, args)

        # Logging period (0 = disabled)
        log_interval = 1e-9 if log_interval is None else float(log_interval)
        if log_interval <= 0:
            log_interval = 1e-9

        # Get progress indication function (if any)
        if progress is None:
            progress = myokit._Simulation_progress
        if progress:
            if not isinstance(progress, myokit.ProgressReporter):
                raise ValueError(
                    'The argument "progress" must be either a subclass of'
                    ' myokit.ProgressReporter or None.')

        # Run simulation
        if duration > 0:
            # Initialize
            state_inf = self._statef
            state_int = self._statet
            state_outf = list(state_inf)
            state_outt = list(state_int)
            self._sim.sim_init(
                platform,
                device,
                kernelf,
                kernelt,
                self._ncellsf[0],
                self._ncellsf[1],
                self._ncellst[0],
                self._ncellst[1],
                self._vmf.indice(),
                self._vmt.indice(),
                self._gf[0],
                self._gf[1],
                self._gt[0],
                self._gt[1],
                self._gft,
                self._cfx,
                self._ctx,
                self._cty,
                tmin,
                tmax,
                self._step_size,
                state_inf,
                state_int,
                state_outf,
                state_outt,
                self._protocol,
                logf,
                logt,
                log_interval,
                [x.qname().encode('ascii') for x in inter_logf],
                [x.qname().encode('ascii') for x in inter_logt],
            )
            try:
                t = tmin
                if progress:
                    # Loop with feedback
                    with progress.job(msg):
                        r = 1.0 / duration if duration != 0 else 1
                        while t < tmax:
                            t = self._sim.sim_step()
                            if t < tmin:
                                # A numerical error has occurred.
                                break
                            if not progress.update(min((t - tmin) * r, 1)):
                                raise myokit.SimulationCancelledError()
                else:
                    # Loop without feedback
                    while t < tmax:
                        t = self._sim.sim_step()
                        if t < tmin:
                            # A numerical error has occurred.
                            break
            finally:
                # Clean even after KeyboardInterrupt or other Exception
                self._sim.sim_clean()
            # Update states
            self._statef = state_outf
            self._statet = state_outt

        # Check for NaN's, print error output
        if report_nan and (logf.has_nan() or logt.has_nan()):
            txt = ['Numerical error found in simulation logs.']
            try:
                # NaN encountered, show how it happened
                part, time, icell, var, value, states, bound = self.find_nan(
                    logf, logt)
                model = self._modelt if part == 'tissue' else self._modelf
                txt.append('Encountered numerical error in ' + part +
                           ' simulation at t=' + str(time) + ' in cell (' +
                           ','.join([str(x) for x in icell]) + ') when ' +
                           var + '=' + str(value) + '.')
                n_states = len(states)
                txt.append('Obtained ' + str(n_states) + ' previous state(s).')
                if n_states > 1:
                    txt.append('State before:')
                    txt.append(model.format_state(states[1]))
                txt.append('State during:')
                txt.append(model.format_state(states[0]))
                if n_states > 1:
                    txt.append('Evaluating derivatives at state before...')
                    try:
                        derivs = model.eval_state_derivatives(
                            states[1], precision=self._precision)
                        txt.append(
                            model.format_state_derivatives(states[1], derivs))
                    except myokit.NumericalError as ee:
                        txt.append(str(ee))
            except myokit.FindNanError as e:
                txt.append(
                    'Unable to pinpoint source of NaN, an error occurred:')
                txt.append(str(e))
            raise myokit.SimulationError('\n'.join(txt))

        # Return logs
        return logf, logt
Beispiel #7
0
    def find_nan(self, logf, logt):
        """
        Searches for the origin of a ``NaN`` (or ``inf``) in a set of
        simulation logs generated by this Simulation.

        The logs must contain the state of each cell and all bound variables.
        The NaN can occur at any point in time except the first.

        Returns a tuple ``(part, time, icell, variable, value, states, bound)``
        where ``time`` is the time the first ``NaN`` was found and ``icell`` is
        the index of the cell in which it happened. The entry ``part`` is a
        string containing either "fiber" or "tissue", indicating which part of
        the simulation triggered the error. The offending variable's name is
        given as ``variable`` and its (illegal) value as ``value``. The current
        state and, if available, any previous states are given in the list
        ``states``. Here, ``states[0]`` points to the current state in the
        simulation part causing the error, ``state[1]`` is the previous state
        and so on. Similarly the values of the error causing model's bound
        variables is given in ``bound``.
        """
        import numpy as np
        # Check if logs contain all states and bound variables
        lt = []
        lf = []
        for label in self._global:
            v = self._modelf.binding(label)
            if v is not None:
                lf.append(v.qname())
            v = self._modelt.binding(label)
            if v is not None:
                lt.append(v.qname())
        lf = myokit.prepare_log(myokit.LOG_STATE + myokit.LOG_BOUND,
                                self._modelf,
                                dims=self._ncellsf,
                                global_vars=lf)
        lt = myokit.prepare_log(myokit.LOG_STATE + myokit.LOG_BOUND,
                                self._modelt,
                                dims=self._ncellst,
                                global_vars=lt)
        for key in lf:
            if key not in logf:
                raise myokit.FindNanError(
                    'Method requires a simulation log from the fiber model'
                    ' containing all states and bound variables. Missing'
                    ' variable <' + key + '>.')
        for key in lt:
            if key not in logt:
                raise myokit.FindNanError(
                    'Method requires a simulation log from the tissue model'
                    ' containing all states and bound variables. Missing at'
                    ' least variable <' + key + '>.')
        del (lf)
        del (lt)

        # Error criterium: NaN/inf detection
        def bisect(ar, lo, hi):
            if not np.isfinite(ar[lo]):
                return lo
            md = lo + int(np.ceil(0.5 * (hi - lo)))
            if md == hi:
                return hi
            if not np.isfinite(ar[md]):
                return bisect(ar, lo, md)
            else:
                return bisect(ar, md, hi)

        # Search for first occurrence of propagating NaN in the log
        def find_error_position(log):
            ifirst = None  # Log list index
            kfirst = None  # Log key
            for key, ar in log.items():
                if ifirst is None:
                    if not np.isfinite(ar[-1]):
                        # First NaN found
                        kfirst = key
                        ifirst = bisect(ar, 0, len(ar) - 1)
                        if ifirst == 0:
                            break
                elif not np.isfinite(ar[ifirst - 1]):
                    # Earlier NaN found
                    kfirst = key
                    ifirst = bisect(ar, 0, ifirst)
                    if ifirst == 0:
                        break
            return ifirst, kfirst

        # Get the name of a time variable in the fiber model
        time_varf = self._modelf.time().qname()

        # Deep searching function
        def relog(_logf, _logt, _dt):
            # Get first occurence of error
            ifirstf, kfirstf = find_error_position(_logf)
            ifirstt, kfirstt = find_error_position(_logt)
            if kfirstf is None and kfirstt is None:
                raise myokit.FindNanError('Error condition not found in logs.')
            elif kfirstf is None:
                ifirst = ifirstt
            elif kfirstt is None:
                ifirst = ifirstf
            elif ifirstf == 0 or ifirstt == 0:
                raise myokit.FindNanError(
                    'Unable to work with simulation logs where the error'
                    ' condition is met in the very first data point.')
            else:
                ifirst = min(ifirstf, ifirstt)
            # Position to start deep search at
            istart = ifirst - 1
            # Get last logged states before error
            statef = []
            statet = []
            for dims in myokit._dimco(*self._ncellsf):
                pre = '.'.join([str(x) for x in dims]) + '.'
                for s in self._modelf.states():
                    statef.append(_logf[pre + s.qname()][istart])
            for dims in myokit._dimco(*self._ncellst):
                pre = '.'.join([str(x) for x in dims]) + '.'
                for s in self._modelt.states():
                    statet.append(_logt[pre + s.qname()][istart])
            # Get last time before error
            time = _logf[time_varf][istart]
            # Save current state & time
            old_statef = self._statef
            old_statet = self._statet
            old_time = self._time
            self._statef = statef
            self._statet = statet
            self._time = time
            # Run until next time point, log every step
            duration = _logf[time_varf][ifirst] - time
            log = myokit.LOG_BOUND + myokit.LOG_STATE
            _logf, _logt = self.run(duration,
                                    logf=log,
                                    logt=log,
                                    log_interval=_dt,
                                    report_nan=False)
            # Reset simulation to original state
            self._statef = old_statef
            self._statet = old_statet
            self._time = old_time
            # Return new logs
            return _logf, _logt

        # Get time step
        dt = logf[time_varf][1] - logf[time_varf][0]

        # Search with successively fine log interval
        while dt > 0:
            dt *= 0.1
            if dt < 0.5:
                dt = 0
            logf, logt = relog(logf, logt, dt)

        # Search for first occurrence of error in the detailed log
        ifirstf, kfirstf = find_error_position(logf)
        ifirstt, kfirstt = find_error_position(logt)
        if kfirstt is None or (kfirstf is not None and kfirstf < kfirstt):
            part = 'fiber'
            ifirst = ifirstf
            kfirst = kfirstf
            model = self._modelf
            log = logf
        else:
            part = 'tissue'
            ifirst = ifirstt
            kfirst = kfirstt
            model = self._modelt
            log = logt

        # Get indices of cell in state vector
        icell = [int(x) for x in kfirst.split('.')[0:2]]

        # Get state & bound before, during and after error
        def state(index, icell):
            s = []
            b = {}
            for var in model.states():
                s.append(log[var.qname(), icell][index])
            for var in model.variables(bound=True):
                if var.binding() in self._global:
                    b[var.qname()] = log[var.qname()][index]
                else:
                    b[var.qname()] = log[var.qname(), icell][index]
            return s, b

        # Get error cell's states before, during and after
        states = []
        bound = []
        max_states = 3
        for k in range(ifirst, ifirst - max_states - 1, -1):
            if k < 0:
                break
            s, b = state(k, icell)
            states.append(s)
            bound.append(b)

        # Get variable causing error
        var = model.get('.'.join(kfirst.split('.')[2:]))

        # Get value causing error
        value = states[1][var.indice()]
        var = var.qname()

        # Get time error occurred
        time = logf[time_varf][ifirst]

        # Return part, time, icell, variable, value, states, bound
        return part, time, icell, var, value, states, bound
Beispiel #8
0
    def _run(self, duration, log, log_interval, report_nan, progress, msg):
        # Simulation times
        if duration < 0:
            raise Exception('Simulation time can\'t be negative.')
        tmin = self._time
        tmax = tmin + duration

        # Gather global variables in model
        g = []
        for label in self._global:
            v = self._model.binding(label)
            if v is not None:
                g.append(v.qname())

        # Parse log argument
        log = myokit.prepare_log(log,
                                 self._model,
                                 dims=self._dims,
                                 global_vars=g,
                                 if_empty=myokit.LOG_STATE + myokit.LOG_BOUND,
                                 allowed_classes=myokit.LOG_STATE +
                                 myokit.LOG_INTER + myokit.LOG_BOUND,
                                 precision=self._precision)

        # Create list of intermediary variables that need to be logged
        inter_log = []
        vars_checked = set()
        for var in log.iterkeys():
            var = myokit.split_key(var)[1]
            if var in vars_checked:
                continue
            vars_checked.add(var)
            var = self._model.get(var)
            if var.is_intermediary() and not var.is_bound():
                inter_log.append(var)

        # Get preferred platform/device combo from configuration file
        platform, device = myokit.OpenCL.load_selection()

        # Compile template into string with kernel code
        kernel_file = os.path.join(myokit.DIR_CFUNC, KERNEL_FILE)
        args = {
            'model': self._model,
            'precision': self._precision,
            'native_math': self._native_math,
            'bound_variables': self._bound_variables,
            'inter_log': inter_log,
            'diffusion': self._diffusion_enabled,
            'fields': self._fields.keys(),
            'paced_cells': self._paced_cells,
        }
        if myokit.DEBUG:
            print('-' * 79)
            print(
                self._code(kernel_file,
                           args,
                           line_numbers=myokit.DEBUG_LINE_NUMBERS))
            import sys
            sys.exit(1)
        kernel = self._export(kernel_file, args)

        # Logging period (0 = disabled)
        log_interval = 1e-9 if log_interval is None else float(log_interval)
        if log_interval <= 0:
            log_interval = 1e-9

        # Create field values vector
        n = len(self._fields) * self._nx * self._ny
        if n:
            field_data = self._fields.itervalues()
            field_data = [np.array(x, copy=False) for x in field_data]
            field_data = np.vstack(field_data)
            field_data = list(field_data.reshape(n, order='F'))
        else:
            field_data = []

        # Get progress indication function (if any)
        if progress is None:
            progress = myokit._Simulation_progress
        if progress:
            if not isinstance(progress, myokit.ProgressReporter):
                raise ValueError(
                    'The argument "progress" must be either a subclass of'
                    ' myokit.ProgressReporter or None.')

        # Run simulation
        arithmetic_error = False
        if duration > 0:
            # Initialize
            state_in = self._state
            state_out = list(state_in)
            self._sim.sim_init(
                platform,
                device,
                kernel,
                self._nx,
                self._ny,
                self._diffusion_enabled,
                self._gx,
                self._gy,
                self._connections,
                tmin,
                tmax,
                self._step_size,
                state_in,
                state_out,
                self._protocol,
                log,
                log_interval,
                [x.qname() for x in inter_log],
                field_data,
            )
            t = tmin
            try:
                if progress:
                    # Loop with feedback
                    with progress.job(msg):
                        r = 1.0 / duration if duration != 0 else 1
                        while t < tmax:
                            t = self._sim.sim_step()
                            if not progress.update(min((t - tmin) * r, 1)):
                                raise myokit.SimulationCancelledError()
                else:
                    # Loop without feedback
                    while t < tmax:
                        t = self._sim.sim_step()
            except ArithmeticError:
                arithmetic_error = True
            finally:
                # Clean even after KeyboardInterrupt or other Exception
                self._sim.sim_clean()
            # Update state
            self._state = state_out

        # Check for NaN
        if report_nan and (arithmetic_error or log.has_nan()):
            txt = ['Numerical error found in simulation logs.']
            try:
                # NaN encountered, show how it happened
                time, icell, var, value, states, bound = self.find_nan(log)
                txt.append('Encountered numerical error at t=' + str(time) +
                           ' in cell (' + ','.join([str(x) for x in icell]) +
                           ') when ' + var + '=' + str(value) + '.')
                n_states = len(states)
                txt.append('Obtained ' + str(n_states) + ' previous state(s).')
                if n_states > 1:
                    txt.append('State before:')
                    txt.append(self._model.format_state(states[1]))
                txt.append('State during:')
                txt.append(self._model.format_state(states[0]))
                if n_states > 1:
                    txt.append('Evaluating derivatives at state before...')
                    try:
                        derivs = self._model.eval_state_derivatives(
                            states[1], precision=self._precision)
                        txt.append(
                            self._model.format_state_derivs(states[1], derivs))
                    except myokit.NumericalError as ee:
                        txt.append(ee.message)
            except myokit.FindNanError as e:
                txt.append(
                    'Unable to pinpoint source of NaN, an error occurred:')
                txt.append(e.message)
            raise myokit.SimulationError('\n'.join(txt))

        # Return log
        return log
Beispiel #9
0
    def find_nan(self, log, watch_var=None, safe_range=None):
        """
        Searches for the origin of a ``NaN`` (or ``inf``) in a simulation log
        generated by this Simulation.

        The log must contain the state of each cell and all bound variables.
        The NaN can occur at any point in time except the first.

        Returns a tuple ``(time, icell, variable, value, states, bound)`` where
        ``time`` is the time the first ``NaN`` was found and ``icell`` is the
        index of the cell in which it happened. The variable's name is given as
        ``variable`` and its (illegal) value as ``value``. The current state
        and, if available, any previous states are given in the list
        ``states``. Here, ``states[0]`` points to the current state,
        ``state[1]`` is the previous state and so on. Similarly the values of
        the model's bound variables is given in ``bound``.

        To aid in diagnosis, a variable can be selected as ``watch_var`` and a
        ``safe_range`` can be specified. With this option, the function will
        find and report either the first ``NaN`` or the first time the watched
        variable left the safe range, whatever came first. The safe range
        should be specified as ``(lower, upper)`` where both bounds are assumed
        to be in the safe range. The watched variable must be a state variable.
        """
        import numpy as np

        # Test if log contains all states and bound variables
        t = []
        for label in self._global:
            var = self._model.binding(label)
            if var is not None:
                t.append(var.qname())
        t = myokit.prepare_log(myokit.LOG_STATE + myokit.LOG_BOUND,
                               self._model,
                               dims=self._dims,
                               global_vars=t)
        for key in t:
            if key not in log:
                raise myokit.FindNanError(
                    'Method requires a simulation log containing all states'
                    ' and bound variables. Missing variable <' + key + '>.')
        del (t)

        # Error criterium
        if watch_var is None:

            # NaN/inf detection
            def bisect(ar, lo, hi):
                if not np.isfinite(ar[lo]):
                    return lo
                md = lo + int(np.ceil(0.5 * (hi - lo)))
                if md == hi:
                    return hi
                if not np.isfinite(ar[md]):
                    return bisect(ar, lo, md)
                else:
                    return bisect(ar, md, hi)

            def find_error_position(log):
                # Search for first occurrence of propagating NaN in the log
                ifirst = None
                kfirst = None
                for key, ar in log.iteritems():
                    if ifirst is None:
                        if not np.isfinite(ar[-1]):
                            # First NaN found
                            kfirst = key
                            ifirst = bisect(ar, 0, len(ar) - 1)
                            if ifirst == 0:
                                break
                    elif not np.isfinite(ar[ifirst - 1]):
                        # Earlier NaN found
                        kfirst = key
                        ifirst = bisect(ar, 0, ifirst)
                        if ifirst == 0:
                            break
                return ifirst, kfirst

        else:

            # Variable out of bounds detection
            try:
                watch_var = self._model.get(watch_var)
            except KeyError:
                raise myokit.FindNanError('Variable <' + str(watch_var) +
                                          '> not found.')
            if not watch_var.is_state():
                raise myokit.FindNanError(
                    'The watched variable must be a state.')
            try:
                lo, hi = safe_range
            except Exception:
                raise myokit.FindNanError(
                    'A safe range must be specified for the watched variable'
                    ' as a tuple (lower, upper).')
            if lo >= hi:
                raise myokit.FindNanError(
                    'The safe range must have a lower bound that is lower than'
                    ' the upper bound.')

            def find_error_position(_log):
                # Find first occurence of out-of-bounds error
                ifirst = None
                kfirst = None
                post = '.' + watch_var.qname()
                lower, upper = safe_range
                for dims in myokit.dimco(*self._dims):
                    key = '.'.join([str(x) for x in dims]) + post
                    ar = np.array(_log[key], copy=False)
                    i = np.where((ar < lower)
                                 | (ar > upper)
                                 | np.isnan(ar)
                                 | np.isinf(ar))[0]
                    if len(i) > 0:
                        i = i[0]
                        if ifirst is None:
                            kfirst = key
                            ifirst = i
                        elif i < ifirst:
                            kfirst = key
                            ifirst = i
                        if i == 0:
                            break
                return ifirst, kfirst

        # Get the name of a time variable
        time_var = self._model.time().qname()

        # Deep searching function
        def relog(_log, _dt):
            # Get first occurence of error
            ifirst, kfirst = find_error_position(_log)
            if kfirst is None:
                raise myokit.FindNanError('Error condition not found in log.')
            if ifirst == 0:
                raise myokit.FindNanError(
                    'Unable to work with simulation logs where the error'
                    ' condition is met in the very first data point.')
            # Position to start deep search at
            istart = ifirst - 1
            # Get last logged state before error
            state = []
            for dims in myokit.dimco(*self._dims):
                pre = '.'.join([str(x) for x in dims]) + '.'
                for s in self._model.states():
                    state.append(_log[pre + s.qname()][istart])
            # Get last time before error
            time = _log[time_var][istart]
            # Save current state & time
            old_state = self._state
            old_time = self._time
            self._state = state
            self._time = time
            # Run until next time point, log every step
            duration = _log[time_var][ifirst] - time
            _log = self.run(duration,
                            log=myokit.LOG_BOUND + myokit.LOG_STATE,
                            log_interval=_dt,
                            report_nan=False)
            # Reset simulation to original state
            self._state = old_state
            self._time = old_time
            # Return new log
            return _log

        # Get time step
        try:
            dt = log[time_var][1] - log[time_var][0]
        except IndexError:
            # Unable to guess dt!
            # So... Nan occurs before the first log interval is reached
            # That probably means dt was relatively large, so guess it was
            # large! Assuming milliseconds, start off with dt=5ms
            dt = 5

        # Search with successively fine log interval
        while dt > 0:
            dt *= 0.1
            if dt < 0.5:
                dt = 0
            log = relog(log, dt)

        # Search for first occurrence of error in the detailed log
        ifirst, kfirst = find_error_position(log)

        # Get indices of cell in state vector
        ndims = len(self._dims)
        icell = [int(x) for x in kfirst.split('.')[0:ndims]]

        # Get state & bound before, during and after error
        def state(index, icell):
            s = []
            b = {}
            for var in self._model.states():
                s.append(log[var.qname(), icell][index])
            for var in self._model.variables(bound=True):
                if var.binding() in self._global:
                    b[var.qname()] = log[var.qname()][index]
                else:
                    b[var.qname()] = log[var.qname(), icell][index]
            return s, b

        # Get error cell's states before, during and after
        states = []
        bound = []
        max_states = 3
        for k in xrange(ifirst, ifirst - max_states - 1, -1):
            if k < 0:
                break
            s, b = state(k, icell)
            states.append(s)
            bound.append(b)

        # Get variable causing error
        var = self._model.get('.'.join(kfirst.split('.')[ndims:]))

        # Get value causing error
        value = states[1][var.indice()]
        var = var.qname()

        # Get time error occurred
        time = log[time_var][ifirst]

        # Return time, icell, variable, value, states, bound
        return time, icell, var, value, states, bound
Beispiel #10
0
    def _run(self, duration, log, log_interval, log_times, sensitivities,
             apd_variable, apd_threshold, progress, msg):

        # Create benchmarker for profiling and realtime logging
        # Note: When adding profiling messages, write them in past tense so
        # that we can show time elapsed for an operation **that has just
        # completed**.
        if myokit.DEBUG_SP or self._model.binding('realtime') is not None:
            b = myokit.tools.Benchmarker()
            if myokit.DEBUG_SP:
                b.print('PP Entered _run method.')
        else:
            b = None

        # Reset error state
        self._error_state = None

        # Simulation times
        if duration < 0:
            raise ValueError('Simulation time can\'t be negative.')
        tmin = self._time
        tmax = tmin + duration

        # Logging interval (None or 0 = disabled)
        log_interval = 0 if log_interval is None else float(log_interval)
        if log_interval < 0:
            log_interval = 0
        if log_times is not None and log_interval > 0:
            raise ValueError(
                'The arguments `log_times` and `log_interval` cannot be used'
                ' simultaneously.')

        # Check user-specified logging times.
        # (An empty list of log points counts as disabled)
        # Note: Checking of values inside the list (converts to float, is non
        # decreasing) happens in the C code.
        if log_times is not None:
            if len(log_times) == 0:
                log_times = None

        # List of sensitivity matrices
        if self._sensitivities:
            if sensitivities is None:
                sensitivities = []
            else:
                # Must be list (of lists of lists)
                if not isinstance(sensitivities, list):
                    raise ValueError(
                        'The argument `sensitivities` must be None or a list.')
        else:
            sensitivities = None

        # APD measuring
        root_list = None
        root_indice = 0
        root_threshold = 0
        if apd_variable is None:
            if apd_threshold is not None:
                raise ValueError(
                    'APD Threshold given but no `apd_variable` specified.')
        else:
            # Get apd variable from this model
            if isinstance(apd_variable, myokit.Variable):
                apd_variable = apd_variable.qname()
            apd_variable = self._model.get(apd_variable)
            if not apd_variable.is_state():
                raise ValueError(
                    'The `apd_variable` must be a state variable.')

            # Set up root finding
            root_list = []
            root_indice = apd_variable.indice()
            root_threshold = float(apd_threshold)

        # Get progress indication function (if any)
        if progress is None:
            progress = myokit._Simulation_progress
        if progress:
            if not isinstance(progress, myokit.ProgressReporter):
                raise ValueError(
                    'The argument `progress` must be either a'
                    ' subclass of myokit.ProgressReporter or None.')
        if myokit.DEBUG_SP:
            b.print('PP Checked arguments.')

        # Parse log argument
        log = myokit.prepare_log(log, self._model, if_empty=myokit.LOG_ALL)
        if myokit.DEBUG_SP:
            b.print('PP Called prepare_log.')

        # Run simulation
        # The simulation is run only if (tmin + duration > tmin). This is a
        # stronger check than (duration == 0), which will return true even for
        # very short durations (and will cause zero iterations of the
        # "while (t < tmax)" loop below).
        if tmin + duration > tmin:

            # Initial state and sensitivities
            state = list(self._state)
            s_state = None
            if self._sensitivities:
                s_state = [list(x) for x in self._s_state]

            # List to store final bound variables in (for debugging)
            bound = [0, 0, 0, 0]

            # Initialize
            if myokit.DEBUG_SP:
                b.print('PP Ready to call sim_init.')
            self._sim.sim_init(
                # 0. Initial time
                tmin,
                # 1. Final time
                tmax,
                # 2. Initial and final state
                state,
                # 3. Initial and final state sensitivities
                s_state,
                # 4. Space to store the bound variable values
                bound,
                # 5. Literal values
                list(self._literals.values()),
                # 6. Parameter values
                list(self._parameters.values()),
                # 7. An event-based pacing protocol
                self._protocol,
                # 8. A fixed-form protocol
                self._fixed_form_protocol,
                # 9. A DataLog
                log,
                # 10. The log interval, or 0
                log_interval,
                # 11. A list of predetermind logging times, or None
                log_times,
                # 12. A list to store calculated sensitivities in
                sensitivities,
                # 13. The state variable indice for root finding (only used if
                #     root_list is a list)
                root_indice,
                # 14. The threshold for root crossing (can be 0 too, only used
                #     if root_list is a list).
                root_threshold,
                # 15. A list to store calculated root crossing times and
                #     directions in, or None
                root_list,
                # 16. A myokit.tools.Benchmarker or None (if not used)
                b,
                # 17. Boolean/int: 1 if we are logging realtime
                int(self._model.binding('realtime') is not None),
            )
            t = tmin

            # Run
            try:
                if progress:
                    # Loop with feedback
                    with progress.job(msg):
                        r = 1.0 / duration if duration != 0 else 1
                        while t < tmax:
                            t = self._sim.sim_step()
                            if not progress.update(min((t - tmin) * r, 1)):
                                raise myokit.SimulationCancelledError()
                else:
                    # Loop without feedback
                    while t < tmax:
                        t = self._sim.sim_step()

            except ArithmeticError as e:
                # Some CVODE(S) errors are set to raise an ArithmeticError,
                # which users may be able to debug.
                if myokit.DEBUG_SP:
                    b.print('PP Caught ArithmeticError.')

                # Store error state
                self._error_state = state

                # Create long error message
                txt = ['A numerical error occurred during simulation at'
                       ' t = ' + str(t) + '.', 'Last reached state: ']
                txt.extend(['  ' + x for x
                            in self._model.format_state(state).splitlines()])
                txt.append('Inputs for binding:')
                txt.append('  time        = ' + myokit.float.str(bound[0]))
                txt.append('  pace        = ' + myokit.float.str(bound[1]))
                txt.append('  realtime    = ' + myokit.float.str(bound[2]))
                txt.append('  evaluations = ' + myokit.float.str(bound[3]))
                txt.append(str(e))

                # Check if state derivatives can be evaluated in Python, if
                # not, add the error to the error message.
                try:
                    self._model.evaluate_derivatives(state)
                except myokit.NumericalError as en:
                    txt.append(str(en))

                # Raise numerical simulation error
                raise myokit.SimulationError('\n'.join(txt))

            except Exception as e:
                if myokit.DEBUG_SP:
                    b.print('PP Caught exception.')

                # Store error state
                self._error_state = state

                # Cast known CVODE errors as SimulationError
                if 'Function CVode()' in str(e):
                    raise myokit.SimulationError(str(e))

                # Unknown exception: re-raise
                raise
            finally:
                # Clean even after KeyboardInterrupt or other Exception
                self._sim.sim_clean()

            # Update internal state
            # Both lists were newly created, so this is OK.
            self._state = state
            self._s_state = s_state

        # Simulation complete
        if myokit.DEBUG_SP:
            b.print('PP Simulation complete.')

        # Calculate apds
        if root_list is not None:
            st = []
            dr = []
            if root_list:
                roots = iter(root_list)
                time, direction = next(roots)
                tlast = time if direction > 0 else None
                for time, direction in roots:
                    if direction > 0:
                        tlast = time
                    else:
                        st.append(tlast)
                        dr.append(time - tlast)
            apds = myokit.DataLog()
            apds['start'] = st
            apds['duration'] = dr
            if myokit.DEBUG_SP:
                b.print('PP Root-finding data processed.')

        # Return
        if myokit.DEBUG_SP:
            b.print('PP Call to _run() complete. Returning.')
        if self._sensitivities is not None:
            if root_list is not None:
                return log, sensitivities, apds
            else:
                return log, sensitivities
        elif root_list is not None:
            return log, apds
        return log