Пример #1
0
 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))
Пример #2
0
    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
Пример #3
0
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)
Пример #4
0
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))
Пример #6
0
    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()
Пример #7
0
    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()
Пример #9
0
    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')
Пример #10
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
Пример #11
0
 def _ex_number(self, e):
     return myokit.strfloat(e) + 'f' if self.sp else myokit.strfloat(e)
Пример #12
0
 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