コード例 #1
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
コード例 #2
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
コード例 #3
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
コード例 #4
0
ファイル: fiber_tissue.py プロジェクト: darmis007/myokit
    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
コード例 #5
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