예제 #1
0
    def test_convert_markov_models_to_compact_form(self):
        # Tests convert_markov_models_to_compact_form()

        # Load clancy model, has two versions of same markov model in it
        fname = os.path.join(DIR_DATA, 'clancy-1999-fitting.mmt')
        model1 = myokit.load_model(fname)

        models = markov.find_markov_models(model1)
        self.assertEqual(len(models), 2)
        m1, m2 = models

        # Check both models are full ODE form
        n1 = sum([1 for x in m1 if x.is_state()])
        self.assertEqual(n1, len(m1))
        n2 = sum([1 for x in m2 if x.is_state()])
        self.assertEqual(n2, len(m2))

        # Convert both compact form
        model2 = markov.convert_markov_models_to_compact_form(model1)
        models = markov.find_markov_models(model2)
        self.assertEqual(len(models), 2)
        m1, m2 = models
        n1 = sum([1 for x in m1 if x.is_state()])
        self.assertEqual(n1, len(m1) - 1)
        n2 = sum([1 for x in m2 if x.is_state()])
        self.assertEqual(n2, len(m2) - 1)

        # Check states evaluate to the same value
        self.assertEqual(
            model1.get('ina.C1').eval(),
            model2.get('ina.C1').eval())
        self.assertEqual(
            model1.get('ina.C2').eval(),
            model2.get('ina.C2').eval())
        self.assertEqual(
            model1.get('ina.C3').eval(),
            model2.get('ina.C3').eval())
        self.assertEqual(
            model1.get('ina.IF').eval(),
            model2.get('ina.IF').eval())
        self.assertEqual(
            model1.get('ina.IS').eval(),
            model2.get('ina.IS').eval())
        self.assertEqual(
            model1.get('ina.O').eval(),
            model2.get('ina.O').eval())

        # Doing it twice should have no effect
        model3 = markov.convert_markov_models_to_compact_form(model2)
        self.assertEqual(model2.code(), model3.code())
예제 #2
0
    def test_find_markov_models(self):
        # Tests find_markov_models()

        # Load clancy model, has two versions of same markov model in it
        fname = os.path.join(DIR_DATA, 'clancy-1999-fitting.mmt')
        model = myokit.load_model(fname)

        models = markov.find_markov_models(model)
        self.assertEqual(len(models), 2)

        # Check states and ordering
        m1, m2 = models
        self.assertEqual(
            [v.qname() for v in m1],
            ['ina.C1', 'ina.C2', 'ina.C3', 'ina.IF', 'ina.IS', 'ina.O'])
        self.assertEqual([v.qname() for v in m2], [
            'ina_ref.C1', 'ina_ref.C2', 'ina_ref.C3', 'ina_ref.IF',
            'ina_ref.IS', 'ina_ref.O'
        ])
        del (models, m1, m2)

        # Try with `1 - sum(xi)` state
        c = model.get('ina_ref')
        v = c.get('C3')
        v.demote()
        v.set_rhs('1 - C1 - C2 - IF - IS - O')
        models = markov.find_markov_models(model)
        self.assertEqual(len(models), 2)
        m1, m2 = models
        self.assertEqual([v.qname() for v in m2], [
            'ina_ref.C1', 'ina_ref.C2', 'ina_ref.C3', 'ina_ref.IF',
            'ina_ref.IS', 'ina_ref.O'
        ])
        del (models, m1, m2)

        # Try with `1 - sum(xi)` state, with a funny RHS
        c = model.get('ina_ref')
        v = c.get('C3')
        v.set_rhs('-(+IF + C1 -(-IS - C2)) + 1 - O')
        models = markov.find_markov_models(model)
        self.assertEqual(len(models), 2)
        m1, m2 = models
        self.assertEqual([v.qname() for v in m2], [
            'ina_ref.C1', 'ina_ref.C2', 'ina_ref.C3', 'ina_ref.IF',
            'ina_ref.IS', 'ina_ref.O'
        ])
예제 #3
0
    def test_find_markov_models_bad(self):
        # Tests find_markov_models() for non-markov models

        # Load clancy model, has two versions of same markov model in it
        fname = os.path.join(DIR_DATA, 'clancy-1999-fitting.mmt')
        moodel = myokit.load_model(fname)

        # Remove ina_ref component
        c = moodel.get('ina_ref')
        for v in c.variables(deep=True):
            v.set_rhs(0)
        for v in list(c.variables(deep=True)):
            c.remove_variable(v, recursive=True)

        # Only one markov model left at this point
        self.assertEqual(len(markov.find_markov_models(moodel)), 1)

        # Check searching states that no-one refers to
        # (And for cases where not each state is a linear combo)
        m = moodel.clone()
        for v in list(m.get('ina.C1').refs_by(True)):
            v.set_rhs(3)
        self.assertEqual(len(markov.find_markov_models(m)), 0)

        # Test with one 1-minus state
        m = moodel.clone()
        v = m.get('ina.C3')
        v.demote()
        v.set_rhs('1 - C1 - C2 - IF - IS - O')
        self.assertEqual(len(markov.find_markov_models(m)), 1)

        # Test without a 1
        v.set_rhs('2 - C1 - C2 - IF - IS - O')
        self.assertEqual(len(markov.find_markov_models(m)), 0)
        v.set_rhs('-C1 - C2 - IF - IS - O')
        self.assertEqual(len(markov.find_markov_models(m)), 0)

        # Test 1-... contains non-linear terms
        v.set_rhs('1 - C1 - C2 - IF - IS - O - O^2')
        self.assertEqual(len(markov.find_markov_models(m)), 0)

        # Test 1-... contains terms with a factor other than -1
        v.set_rhs('1 - C1 - C2 - IF - IS - O - O')
        self.assertEqual(len(markov.find_markov_models(m)), 0)

        # Test if there's multiple variables with a 1-... RHS
        m = moodel.clone()
        v = m.get('ina.C3')
        v.demote()
        v.set_rhs('1 - C1 - C2 - IF - IS - O')
        self.assertEqual(len(markov.find_markov_models(m)), 1)
        v = m.get('ina').add_variable('C4')
        v.set_rhs('1 - C1 - C2 - IF - IS - O')
        self.assertEqual(len(markov.find_markov_models(m)), 0)

        # But having an extra variable is fine, if the rest checks out!
        m = moodel.clone()
        v = m.get('ina').add_variable('C4')
        v.set_rhs('1 - C1 - C2 - C3 - IF - IS - O')
        self.assertEqual(len(markov.find_markov_models(m)), 1)

        # Must have at least two states
        m = myokit.Model()
        c = m.add_component('c')
        t = c.add_variable('time')
        t.set_binding('time')
        v = c.add_variable('v')
        v.promote(0.1)
        self.assertEqual(len(markov.find_markov_models(m)), 0)
예제 #4
0
    def model(self, path, model):
        """
        Exports a :class:`myokit.Model` in EasyML format, writing the result to
        the file indicated by ``path``.

        A :class:`myokit.ExportError` will be raised if any errors occur.
        """
        import myokit.formats.easyml as easyml

        # Test model is valid
        try:
            model.validate()
        except myokit.MyokitError:
            raise myokit.ExportError('EasyML export requires a valid model.')

        # Rewrite model so that any Markov models have a 1-sum(...) state
        # This also clones the model, so that changes can be made
        model = markov.convert_markov_models_to_compact_form(model)

        # Replace any RHS references to state derivatives with references to
        # intermediary variables
        model.remove_derivative_references()

        # Remove hardcoded stimulus protocol, if any
        guess.remove_embedded_protocol(model)

        # List of variables not to output
        ignore = set()

        # Find membrane potential
        vm = guess.membrane_potential(model)

        # Ensure vm is a state, so that expressions depending on V are not seen
        # as constants.
        if not vm.is_state():
            vm.promote(-80)

        # Don't output vm
        ignore.add(vm)

        # Get time variable
        time = model.time()

        # Find currents (must be done before i_ion is removed)
        currents = guess.membrane_currents(model)

        # Use recommended units
        '''
        recommended_current_units = [
            myokit.parse_unit('pA/pF'),
            myokit.parse_unit('uA/cm^2'),
        ]
        helpers =
        if time.unit() is not None:
            time.convert_unit(myokit.units.ms)
        if vm.unit() is not None:
            vm.convert_unit(myokit.units.mV)
        for current in currents:
            if current.unit() is not None:
                if current.unit() not in recommended_current_units:
                    current.convert_unit('uA/cm^2', helpers=helpers)
        '''

        # Remove time, pacing variable, diffusion current, and i_ion
        pace = model.binding('pace')
        i_diff = model.binding('diffusion_current')
        i_ion = model.label('cellular_current')
        for var in [time, pace, i_diff, i_ion]:
            if var is None:
                continue

            # Replace references to these variables with 0
            refs = list(var.refs_by())
            subst = {var.lhs(): myokit.Number(0)}
            for ref in refs:
                ref.set_rhs(ref.rhs().clone(subst=subst))

            # Remove variable
            var.parent().remove_variable(var)

            # Remove from currents, if present
            try:
                currents.remove(var)
            except ValueError:
                pass

        # Remove unused variables

        # Start by setting V's rhs to the sum of currents, so all currents
        # count as used
        rhs = myokit.Number(0)
        for v in currents:
            rhs = myokit.Plus(rhs, v.lhs())
        vm.set_rhs(rhs)

        # Remove all bindings and labels (so they register as unused)
        for b, var in model.bindings():
            var.set_binding(None)
        for b, var in model.labels():
            var.set_label(None)

        # And add the time variable back in
        time = vm.parent().add_variable(time.name())
        time.set_rhs(0)
        time.set_binding('time')
        ignore.add(time)

        # Remove unused
        model.validate(remove_unused_variables=True)

        # Find special variables
        hh_states = set()
        alphas = {}
        betas = {}
        taus = {}
        infs = {}

        # If an inf/tau/alpha/beta is used by more than one state, we create a
        # copy for each user, so that we can give the variables the appropriate
        # name (e.g. x_inf, y_inf, z_inf) etc.
        def renamable(var):
            srefs = [v for v in var.refs_by() if v.is_state()]
            if len(srefs) > 1:
                v = var.parent().add_variable_allow_renaming(var.name())
                v.set_rhs(myokit.Name(var))
                v.set_unit(var.unit())
                return v
            return var

        # Find HH state variables and their infs, taus, alphas, betas
        #
        for var in model.states():
            ret = hh.get_inf_and_tau(var, vm)
            if ret is not None:
                ignore.add(var)
                hh_states.add(var)
                infs[renamable(ret[0])] = var
                taus[renamable(ret[1])] = var
                continue
            ret = hh.get_alpha_and_beta(var, vm)
            if ret is not None:
                ignore.add(var)
                hh_states.add(var)
                alphas[renamable(ret[0])] = var
                betas[renamable(ret[1])] = var
                continue

        hh_variables = (set(alphas.keys()) | set(betas.keys())
                        | set(taus.keys()) | set(infs.keys()))

        # Create variable names
        # The following strategy is followed:
        #  - HH variables are named after their state
        #  - All other variables are checked for special names, and changed if
        #    necessary
        #  - Any conflicts are resolved in a final step

        # Detect names with special meanings
        def special_start(name):
            if name.startswith('diff_') or name.startswith('d_'):
                return True
            if name.startswith('alpha_') or name.startswith('a_'):
                return True
            if name.startswith('beta_') or name.startswith('b_'):
                return True
            if name.startswith('tau_'):
                return True
            return False

        def special_end(name):
            return name.endswith('_init') or name.endswith('_inf')

        # Create initial variable names
        var_to_name = {}
        for var in model.variables(deep=True):

            # Delay naming of HH variables until their state has a name
            if var in hh_variables:
                continue

            # Start from simple variable name
            name = var.name()

            # Add parent if needed
            if name in ['alpha', 'beta', 'inf', 'tau']:
                name = var.parent().name() + '_' + name

            # Avoid names with special meaning
            if special_end(name):
                name += '_var'
            if special_start(name):
                name = var.parent().name() + '_' + name
                if special_start(name):
                    name = var.qname().replace('.', '_')
                if special_start(name):
                    name = 'var_' + name

            # Store (initial) name for var
            var_to_name[var] = name

        # Check for conflicts with known keywords
        from . import keywords
        reserved = [
            'Iion',
        ]
        needs_renaming = {}
        for keyword in keywords:
            needs_renaming[keyword] = []
        for keyword in reserved:
            needs_renaming[keyword] = []

        # Find naming conflicts, create inverse mapping
        name_to_var = {}
        for var, name in var_to_name.items():

            # Known conflict?
            if name in needs_renaming:
                needs_renaming[name].append(var)
                continue

            # Test for new conflicts
            var2 = name_to_var.get(name, None)
            if var2 is not None:
                needs_renaming[name] = [var2, var]
            else:
                name_to_var[name] = var

        # Resolve naming conflicts
        for name, variables in needs_renaming.items():
            # Add a number to the end of the name, increasing it until it's
            # unique
            i = 1
            root = name + '_'
            for var in variables:
                name = root + str(i)
                while name in name_to_var:
                    i += 1
                    name = root + str(i)
                var_to_name[var] = name
                name_to_var[name] = var

        # Remove reverse mappings
        del (name_to_var, needs_renaming)

        # Create names for HH variables
        for var, state in alphas.items():
            var_to_name[var] = 'alpha_' + var_to_name[state]
        for var, state in betas.items():
            var_to_name[var] = 'beta_' + var_to_name[state]
        for var, state in taus.items():
            var_to_name[var] = 'tau_' + var_to_name[state]
        for var, state in infs.items():
            var_to_name[var] = var_to_name[state] + '_inf'

        # Create naming function
        def lhs(e):
            if isinstance(e, myokit.Derivative):
                return 'diff_' + var_to_name[e.var()]
            elif isinstance(e, myokit.LhsExpression):
                return var_to_name[e.var()]
            elif isinstance(e, myokit.Variable):
                return var_to_name[e]
            raise ValueError(  # pragma: no cover
                'Not a variable or LhsExpression: ' + str(e))

        # Create expression writer
        e = easyml.EasyMLExpressionWriter()
        e.set_lhs_function(lhs)

        # Test if can write in dir (raises exception if can't)
        self._test_writable_dir(os.path.dirname(path))

        # Write equations
        eol = '\n'
        eos = ';\n'
        with open(path, 'w') as f:
            # Write membrane potential
            f.write(lhs(vm) + '; .nodal(); .external(Vm);' + eol)

            # Write current
            f.write('Iion; .nodal(); .external();' + eol)
            f.write(eol)

            # Write initial conditions
            for v in model.states():
                f.write(
                    lhs(v) + '_init = ' + myokit.strfloat(v.state_value()) +
                    eos)
            f.write(eol)

            # Write remaining variables
            for c in model.components():
                todo = [v for v in c.variables(deep=True) if v not in ignore]
                if todo:
                    f.write('// ' + c.name() + eol)
                    for v in todo:
                        f.write(e.eq(v.eq()) + eos)
                    f.write(eol)

            # Write solution methods for Markov models
            for states in markov.find_markov_models(model):
                f.write('// Markov model' + eol)
                f.write('group {' + eol)
                for state in states:
                    if state.is_state():
                        f.write('  ' + lhs(state) + eos)
                f.write('} .method(markov_be)' + eos)
                f.write(eol)

            # Write sum of currents variable
            f.write('// Sum of currents' + eol)
            f.write('Iion = ')
            f.write(' + '.join([lhs(v) for v in currents]))
            f.write(eos)
            f.write(eol)