def test_strfloat(self): # Deprecated alias of myokit.float.str args = ['-1.234', True, myokit.SINGLE_PRECISION] with WarningCollector() as c: x = myokit.strfloat(*args) self.assertIn('`myokit.strfloat` is deprecated', c.text()) self.assertEqual(x, myokit.float.str(*args))
def _variable(self, parent, variable): """ Adds a ``variable`` element to ``parent`` for the variable represented by :class:`myokit.formats.cellml.v1.Variable` ``variable``. """ # Create element element = etree.SubElement(parent, 'variable') element.attrib['name'] = variable.name() # Add units element.attrib['units'] = variable.units().name() # Add interfaces if variable.public_interface() != 'none': element.attrib['public_interface'] = variable.public_interface() if variable.private_interface() != 'none': element.attrib['private_interface'] = variable.private_interface() # Add initial value if variable.initial_value() is not None: element.attrib['initial_value'] = myokit.strfloat( variable.initial_value()).strip() # Add cmeta id cid = variable.cmeta_id() if cid is not None: element.attrib[etree.QName(cellml.NS_CMETA, 'id')] = cid # Add oxmeta annotation, if found try: self._oxmeta_variables[cid] = variable.meta['oxmeta'] except KeyError: pass
def save_state(filename, state, model=None): """ Stores the given state in the file at ``filename``. If no ``model`` is specified ``state`` should be given as a list of floating point numbers and will be stored by simply placing each number on a new line. If a :class:`Model <myokit.Model>` is provided the state can be in any format accepted by :meth:`Model.map_to_state() <myokit.Model.map_to_state>` and will be stored in the format returned by :meth:`Model.format_state() <myokit.Model.format_state>`. """ # Check filename filename = os.path.expanduser(filename) # Format if model is not None: state = model.map_to_state(state) state = model.format_state(state) else: state = [myokit.strfloat(s) for s in state] state = '\n'.join(state) # Store with open(filename, 'w') as f: f.write(state)
def save_state(filename, state, model=None): """ Stores the given state in the file at ``filename``. If no ``model`` is specified ``state`` should be given as a list of floating point numbers and will be stored by simply placing each number on a new line. If a :class:`Model <myokit.Model>` is provided the state can be in any format accepted by :meth:`Model.map_to_state() <myokit.Model.map_to_state>` and will be stored in the format returned by :meth:`Model.format_state() <myokit.Model.format_state>`. """ # Check filename filename = os.path.expanduser(filename) # Format if model is not None: state = model.map_to_state(state) state = model.format_state(state) else: s = [] try: state = [myokit.strfloat(s) for s in state] state = '\n'.join(state) except Exception: raise Exception( # TODO Specific error should be caught # TODO Message should be much much clearer 'State must be given as list (or other iterable) or' ' save_state() must be used with the third argument `model`' ' set.') with open(filename, 'w') as f: f.write(state)
def step(source, ref, ini, raw): """ Loads a model and evaluates the state vector derivatives. """ import sys import myokit # Parse reference file, if given if ref and not raw: print('Reading reference file...') try: ref = myokit.load_model(ref[0]) print('Reference model loaded successfully.') except Exception: ref = myokit.load_state(ref[0]) print('Reference file read successfully.') # Parse initial value file, if given if ini: if not raw: print('Reading initial value file...') ini = myokit.load_state(ini[0]) if not raw: print('Initial value file read successfully.') # Load myokit model try: if not raw: print('Reading model from ' + source + '...') model = myokit.load_model(source) if not raw: print('Model ' + model.name() + ' read successfully.') except myokit.ParseError as ex: print(myokit.format_parse_error(ex, source)) sys.exit(1) # Ensure proper ordering of reference and initial value files if ref and not isinstance(ref, myokit.Model): ref = model.map_to_state(ref) # Evaluate all derivatives, show the results try: if raw: derivs = model.eval_state_derivatives(state=ini) print('\n'.join([myokit.strfloat(x) for x in derivs])) else: print(myokit.step(model, initial=ini, reference=ref)) except myokit.NumericalError as ee: e = 'Numerical error' n = line_width - len(e) - 2 print('-' * int(n / 2) + ' ' + e + ' ' + '-' * (n - int(n / 2))) print('A numerical error occurred:') print(str(ee))
def _units(self, parent, units): """ Adds a ``units`` element to ``parent``, for the given CellML units object. """ # Get unit exponents on first call if self._exp_si is None: from myokit.formats.cellml import v2 self._exp_si = [ v2.Units._si_units_r[x] for x in myokit.Unit.list_exponents() ] # Create units element element = etree.SubElement(parent, 'units') element.attrib['name'] = units.name() # Get myokit unit myokit_unit = units.myokit_unit() # Add unit row for each of the 7 SI units needed to make up this unit rows = [] for k, e in enumerate(myokit_unit.exponents()): if e != 0: row = etree.SubElement(element, 'unit') row.attrib['units'] = self._exp_si[k] if e != 1: row.attrib['exponent'] = str(e) # Must be an integer rows.append(row) # Handle dimensionless units with a multiplier if not rows: row = etree.SubElement(element, 'unit') row.attrib['units'] = 'dimensionless' rows.append(row) # Add multiplier or prefix to first row multiplier = myokit_unit.multiplier() if multiplier != 1: if myokit._feq(multiplier, int(multiplier)): rows[0].attrib['multiplier'] = str(int(multiplier)) else: rows[0].attrib['multiplier'] = myokit.strfloat( multiplier).strip()
def _variable(self, parent, variable): """ Adds a ``variable`` element to ``parent`` for the variable represented by :class:`myokit.formats.cellml.v2.Variable` ``variable``. """ # Create element element = etree.SubElement(parent, 'variable') element.attrib['name'] = variable.name() # Add units element.attrib['units'] = variable.units().name() # Add interfaces if variable.interface() != 'none': element.attrib['interface'] = variable.interface() # Add initial value if variable is variable.initial_value_variable(): element.attrib['initial_value'] = myokit.strfloat( variable.initial_value()).strip()
def model( self, path, model, protocol=None, add_hardcoded_pacing=True, pretty_xml=True): """ Writes a CellML model to the given filename. Arguments: ``path`` The path/filename to write the generated code too. ``model`` The model to export ``protocol`` This argument will be ignored: protocols are not supported by CellML. ``add_hardcoded_pacing`` Set this to ``True`` to add a hardcoded pacing signal to the model file. This requires the model to have a variable bound to `pace`. ``pretty_xml`` Set this to ``True`` to write the output in formatted "pretty" xml. Notes about CellML export: * CellML expects a unit for every number present in the model. Since Myokit allows but does not enforce this, the resulting CellML file may only validate with unit checking disabled. * Files downloaded from the CellML repository typically have a pacing stimulus embedded in them, while Myokit views models and pacing protocols as separate things. To generate a model file with a simple embbeded protocol, add the optional argument ``add_hardcoded_pacing=True``. """ path = os.path.abspath(os.path.expanduser(path)) import myokit.formats.cellml as cellml # Clear log self.logger().clear() self.logger().clear_warnings() # Replace the pacing variable with a hardcoded stimulus protocol if add_hardcoded_pacing: # Check for pacing variable if model.binding('pace') is None: self.logger().warn( 'No variable bound to "pace", unable to add hardcoded' ' stimulus protocol.') else: # Clone model before making changes model = model.clone() # Get pacing variable pace = model.binding('pace') # Set basic properties for pace pace.set_unit(myokit.units.dimensionless) pace.set_rhs(0) pace.set_binding(None) pace.set_label(None) # Should already be true... # Get time variable of cloned model time = model.time() # Get time unit time_unit = time.unit(mode=myokit.UNIT_STRICT) # Get correction factor if using anything other than # milliseconds (hardcoded below) try: time_factor = myokit.Unit.conversion_factor( 'ms', time_unit) except myokit.IncompatibleUnitError: time_factor = 1 # Create new component for the pacing variables component = 'stimulus' if model.has_component(component): root = component number = 1 while model.has_component(component): number += 1 component = root + '_' + str(number) component = model.add_component(component) # Move pace. This will be ok any references: since pace was # bound it cannot be a nested variable. # While moving, update its name to avoid conflicts with the # hardcoded names. pace.parent().move_variable(pace, component, new_name='pace') # Add variables defining pacing protocol period = component.add_variable('period') period.set_unit(time_unit) period.set_rhs(str(1000 * time_factor) + ' ' + str(time_unit)) offset = component.add_variable('offset') offset.set_unit(time_unit) offset.set_rhs(str(100 * time_factor) + ' ' + str(time_unit)) duration = component.add_variable('duration') duration.set_unit(time_unit) duration.set_rhs(str(2 * time_factor) + ' ' + str(time_unit)) # Add corrected time variable ctime = component.add_variable('ctime') ctime.set_unit(time_unit) ctime.set_rhs( time.qname() + ' - floor(' + time.qname() + ' / period) * period') # Remove any child variables pace might have before changing # its RHS (which needs to refer to them). pace_kids = list(pace.variables()) for kid in pace_kids: pace.remove_variable(kid, recursive=True) # Set new RHS for pace pace.set_rhs( 'if(ctime >= offset and ctime < offset + duration, 1, 0)') # Validate model model.validate() # Get time variable time = model.time() # Create model xml element emodel = et.Element('model') emodel.attrib['xmlns'] = 'http://www.cellml.org/cellml/1.0#' emodel.attrib['xmlns:cellml'] = 'http://www.cellml.org/cellml/1.0#' emodel.attrib['name'] = 'generated_model' if 'name' in model.meta: dtag = et.SubElement(emodel, 'documentation') dtag.attrib['xmlns'] = 'http://cellml.org/tmp-documentation' atag = et.SubElement(dtag, 'article') ttag = et.SubElement(atag, 'title') ttag.text = model.meta['name'] # Add custom units, create unit map exp_si = [si_units[x] for x in myokit.Unit.list_exponents()] unit_map = {} # Add si units later def add_unit(unit): """ Checks if the given unit needs to be added to the list of custom units and adds it if necessary. """ # Check if already defined if unit is None or unit in unit_map or unit in si_units: return # Create unit name name = self.custom_unit_name(unit) # Create unit tag utag = et.SubElement(emodel, 'units') utag.attrib['name'] = name # Add part for each of the 7 SI units m = unit.multiplier() for k, e in enumerate(unit.exponents()): if e != 0: tag = et.SubElement(utag, 'unit') tag.attrib['units'] = exp_si[k] tag.attrib['exponent'] = str(e) if m != 1: tag.attrib['multiplier'] = str(m) m = 1 # Or... if the unit doesn't contain any of those seven, it must be # a dimensionless unit with a multiplier. These occur in CellML # definitions when unit mismatches are "resolved" by adding # conversion factors as units. This has no impact on the actual # equations... if m != 1: tag = et.SubElement(utag, 'unit') tag.attrib['units'] = si_units[myokit.units.dimensionless] tag.attrib['exponent'] = str(1) tag.attrib['multiplier'] = str(m) # m = 1 # Add the new unit to the list unit_map[unit] = name # Add variable and expression units for var in model.variables(deep=True): add_unit(var.unit()) for e in var.rhs().walk(myokit.Number): add_unit(e.unit()) # Add si units to unit map for unit, name in si_units.items(): unit_map[unit] = name # Add components #TODO: Order components # Components can correspond to Myokit components or variables with # children! ecomps = {} # Components/Variables: elements (tags) cnames = {} # Components/Variables: names (strings) unames = set() # Unique name check def uname(name): # Create a unique component name i = 1 r = name + '_' while name in unames: i += 1 name = r + str(i) return name def export_nested_var(parent_tag, parent_name, var): # Create unique component name cname = uname(parent_name + '_' + var.uname()) cnames[var] = cname unames.add(cname) # Create element ecomp = et.SubElement(emodel, 'component') ecomp.attrib['name'] = cname ecomps[var] = ecomp # Check for nested variables with children for kid in var.variables(): if kid.has_variables(): export_nested_var(ecomp, cname, kid) for comp in model.components(): # Create unique name cname = uname(comp.name()) cnames[comp] = cname unames.add(cname) # Create element ecomp = et.SubElement(emodel, 'component') ecomp.attrib['name'] = cname ecomps[comp] = ecomp # Check for variables with children for var in comp.variables(): if var.has_variables(): export_nested_var(ecomp, cname, var) # Add variables evars = {} for parent, eparent in ecomps.items(): for var in parent.variables(): evar = et.SubElement(eparent, 'variable') evars[var] = evar evar.attrib['name'] = var.uname() # Add units unit = var.unit() unit = unit_map[unit] if unit else 'dimensionless' evar.attrib['units'] = unit # Add initial value init = None if var.is_literal(): init = var.rhs().eval() elif var.is_state(): init = var.state_value() if init is not None: evar.attrib['initial_value'] = myokit.strfloat(init) # Add variable interfaces, connections deps = model.map_shallow_dependencies( omit_states=False, omit_constants=False) for var, evar in evars.items(): # Scan all variables, iterate over the vars they depend on par = var.parent() lhs = var.lhs() dps = set(deps[lhs]) if var.is_state(): # States also depend on the time variable dps.add(time.lhs()) for dls in dps: dep = dls.var() dpa = dep.parent() # Parent mismatch: requires connection if par != dpa: # Check if variable tag is present epar = ecomps[par] tag = epar.find('variable[@name="' + dep.uname() + '"]') if tag is None: # Create variable tag tag = et.SubElement(epar, 'variable') tag.attrib['name'] = dep.uname() # Add unit unit = dep.unit() unit = unit_map[unit] if unit else 'dimensionless' tag.attrib['units'] = unit # Set interfaces tag.attrib['public_interface'] = 'in' edpa = ecomps[dpa] tag = edpa.find( 'variable[@name="' + dep.uname() + '"]') tag.attrib['public_interface'] = 'out' # Add connection for this variable comp1 = cnames[par] comp2 = cnames[dpa] vname = dep.uname() # Sort components in connection alphabetically to # ensure uniqueness if comp2 < comp1: comp1, comp2 = comp2, comp1 # Find or create connection ctag = None for con in emodel.findall('connection'): ctag = con.find( 'map_components[@component_1="' + comp1 + '"][@component_2="' + comp2 + '"]') if ctag is not None: break if ctag is None: con = et.SubElement(emodel, 'connection') ctag = et.SubElement(con, 'map_components') ctag.attrib['component_1'] = comp1 ctag.attrib['component_2'] = comp2 vtag = con.find( 'map_variables[@variable_1="' + vname + '"][variable_2="' + vname + '"]') if vtag is None: vtag = et.SubElement(con, 'map_variables') vtag.attrib['variable_1'] = vname vtag.attrib['variable_2'] = vname # Create CellMLWriter writer = cellml.CellMLExpressionWriter(units=unit_map) writer.set_element_tree_class(et) writer.set_time_variable(time) # Add equations def add_child_equations(parent): # Add the equations to a cellml component try: ecomp = ecomps[parent] except KeyError: return maths = et.SubElement(ecomp, 'math') maths.attrib['xmlns'] = 'http://www.w3.org/1998/Math/MathML' for var in parent.variables(): if var.is_literal(): continue writer.eq(var.eq(), maths) add_child_equations(var) for comp in model.components(): add_child_equations(comp) # Write xml to file doc = et.ElementTree(emodel) doc.write(path, encoding='utf-8', method='xml') if pretty_xml: # Create pretty XML import xml.dom.minidom as m xml = m.parse(path) with open(path, 'wb') as f: f.write(xml.toprettyxml(encoding='utf-8')) # Log any generated warnings self.logger().log_warnings()
def test_myokit_strfloat(self): # Test float to string conversion. # String should be passed through # Note: convert to str() to test in python 2 and 3. self.assertEqual(myokit.strfloat(str('123')), '123') # Simple numbers self.assertEqual(myokit.strfloat(0), '0') self.assertEqual(myokit.strfloat(0.0000), '0.0') self.assertEqual(myokit.strfloat(1.234), '1.234') self.assertEqual(myokit.strfloat(0.12432656245e12), ' 1.24326562450000000e+11') self.assertEqual(myokit.strfloat(-0), '0') self.assertEqual(myokit.strfloat(-0.0000), '-0.0') self.assertEqual(myokit.strfloat(-1.234), '-1.234') self.assertEqual(myokit.strfloat(-0.12432656245e12), '-1.24326562450000000e+11') # Strings are not converted x = '1.234' self.assertEqual(x, myokit.strfloat(x)) # Myokit Numbers are converted x = myokit.Number(1.23) self.assertEqual(myokit.strfloat(x), '1.23') # Single and double precision self.assertEqual( myokit.strfloat(-1.234, precision=myokit.SINGLE_PRECISION), '-1.234') self.assertEqual( myokit.strfloat(-0.124326562458734682153498731245756e12, precision=myokit.SINGLE_PRECISION), '-1.243265625e+11') self.assertEqual( myokit.strfloat(-1.234, precision=myokit.DOUBLE_PRECISION), '-1.234') self.assertEqual( myokit.strfloat(-0.124326562458734682153498731245756e12, precision=myokit.DOUBLE_PRECISION), '-1.24326562458734680e+11') # Full precision override self.assertEqual(myokit.strfloat(1.23, True), ' 1.22999999999999998e+00') self.assertEqual(myokit.strfloat(1.23, True, myokit.DOUBLE_PRECISION), ' 1.22999999999999998e+00') self.assertEqual(myokit.strfloat(1.23, True, myokit.SINGLE_PRECISION), ' 1.230000000e+00')
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 _ex_number(self, e): return myokit.strfloat(e) + 'f' if self.sp else myokit.strfloat(e)
def _ex_number(self, e): return myokit.strfloat(e)
def save_atf(log, filename, fields=None): """ Saves the :class:`myokit.DataLog` ``log`` to ``filename`` in ATF format. ATF requires that the times in the log be regularly spaced. The first column in an ATF file should always be time. Remaining fields will be written in a random order. To indicate an order or make a selection of fields, pass in a sequence ``fields`` containing the field names. """ log.validate() import myokit # Check filename filename = os.path.expanduser(filename) # Delimiters # Dos-style EOL: Open file in 'wb' mode to stop windows writing \r\r\n eol = '\r\n' delim = '\t' # Create data and keys lists data = [iter(log.time())] time = log.time_key() keys = [time] # Check fields if fields: for field in fields: field = str(field) if field == time: continue keys.append(field) try: data.append(iter(log[field])) except KeyError: raise ValueError('Variable <' + field + '> not found in log.') else: for k, v in log.items(): if k != time: keys.append(k) data.append(iter(v)) for k in keys: if '"' in k: raise ValueError('Column names must not contain double quotes.') if '\r' in k or '\n' in k: raise ValueError( 'Column names must not contain newlines or carriage returns.') # Check if time is equally spaced t = np.asarray(log.time()) dt = t[1:] - t[:-1] dt_ref = dt[0] dt_err = dt_ref * 1e-6 if np.any(np.abs(dt - dt_ref) > dt_err): raise ValueError('The time variable must be regularly spaced.') # Create header header = [] header.append(('myokit-version', 'Myokit ' + myokit.version(raw=True))) header.append(('date-created', myokit.date())) header.append(('sampling-interval', dt_ref)) # Get sizes nh = len(header) nf = len(keys) nd = log.length() # Write file with open(filename, 'wb') as f: # Write version number f.write(('ATF 1.0' + eol).encode(_ENC)) # Write number of header lines, number of fields f.write((str(nh) + delim + str(nf) + eol).encode(_ENC)) for k, v in header: f.write(('"' + str(k) + '=' + str(v) + '"' + eol).encode(_ENC)) # Write field names f.write((delim.join(['"' + k + '"' for k in keys]) + eol).encode(_ENC)) # Write data for i in range(nd): f.write(( delim.join([myokit.strfloat(next(d)) for d in data]) + eol ).encode(_ENC))
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 with myokit.SubCapture() as capture: 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, log_times, root_list, root_threshold, bench, ) t = tmin try: if progress: # Allow progress reporters to bypass the subcapture progress._set_output_stream(capture.bypass()) # 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: 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(str(en)) raise myokit.SimulationError('\n'.join(txt)) except Exception as e: # Store error state self._error_state = list(state) # 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 = 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 = 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