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