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
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
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
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
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
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
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
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
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