示例#1
0
    def __init__(self, model, protocol=None, ncells=50, rl=False):
        super(Simulation1d, self).__init__()

        # Require a valid model
        model.validate()

        # Set protocol
        self.set_protocol(protocol)

        # Set number of cells
        ncells = int(ncells)
        if ncells < 1:
            raise ValueError('The number of cells must be at least 1.')
        self._ncells = ncells

        # Set rush-larsen mode
        self._rl = bool(rl)

        # Get membrane potential variable
        vm = model.label('membrane_potential')
        if vm is None:
            raise ValueError(
                'This simulation requires the membrane potential'
                ' variable to be labelled as "membrane_potential".')

        # Prepare for Rush-Larsen updates, and/or clone model
        rl_states = {}
        if self._rl:
            import myokit.lib.hh as hh

            # Convert alpha-beta formulations to inf-tau forms, cloning model
            self._model = hh.convert_hh_states_to_inf_tau_form(model, vm)
            self._vm = self._model.get(vm.qname())
            del (model, vm)

            # Get (inf, tau) tuple for every Rush-Larsen state
            for state in self._model.states():
                res = hh.get_inf_and_tau(state, self._vm)
                if res is not None:
                    rl_states[state] = res

        else:
            # Clone model, store
            self._model = model.clone()
            self._vm = self._model.get(vm.qname())
            del (model, vm)

        # Set number of cells paced
        self.set_paced_cells()

        # Set conductance
        self.set_conductance()

        # Set step size
        self.set_step_size()

        # Set remaining properties
        self._time = 0
        self._nstate = self._model.count_states()

        # Get membrane potential variable
        self._vm = self._model.label('membrane_potential')
        # Already checked this above
        # if self._vm is None:
        #    raise ValueError(
        #        'This simulation requires the membrane potential'
        #        ' variable to be labelled as "membrane_potential".')

        # Check for binding to diffusion_current
        if self._model.binding('diffusion_current') is None:
            raise ValueError(
                'This simulation requires a variable to be bound to'
                ' "diffusion_current" to pass current from one cell to the'
                ' next')

        # Set state and default state
        self._state = self._model.state() * ncells
        self._default_state = list(self._state)

        # Unique simulation id
        Simulation1d._index += 1
        module_name = 'myokit_sim1d_' + str(Simulation1d._index)
        module_name += '_' + str(myokit.pid_hash())

        # Arguments
        args = {
            'module_name': module_name,
            'model': self._model,
            'vmvar': self._vm,
            'ncells': self._ncells,
            'rl_states': rl_states,
        }
        fname = os.path.join(myokit.DIR_CFUNC, SOURCE_FILE)

        # Define libraries
        libs = []
        if platform.system() != 'Windows':  # pragma: no windows cover
            libs.append('m')

        # Create simulation
        libd = None
        incd = [myokit.DIR_CFUNC]
        self._sim = self._compile(module_name, fname, args, libs, libd, incd)
示例#2
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)
    def _vars(self, model, protocol):
        from myokit.formats.opencl import keywords

        # Check if model has binding to diffusion_current
        if model.binding('diffusion_current') is None:
            raise ValueError('No variable bound to `diffusion_current`.')

        # Check if model has label membrane_potential
        if model.label('membrane_potential') is None:
            raise ValueError('No variable labelled `membrane_potential`.')

        # Clone model, and adapt to inf-tau form if in RL mode
        rl_states = {}
        if self._use_rl:
            # Convert model to inf-tau form (returns clone) and get vm
            import myokit.lib.hh as hh
            model = hh.convert_hh_states_to_inf_tau_form(model)
            vm = model.label('membrane_potential')

            # Get (inf, tau) tuple for every Rush-Larsen state
            for state in model.states():
                res = hh.get_inf_and_tau(state, vm)
                if res is not None:
                    rl_states[state] = res

        else:
            model = model.clone()

        # Merge interdependent components
        model.resolve_interdependent_components()

        # Process bindings, remove unsupported bindings, get map of bound
        # variables to internal names.
        bound_variables = model.prepare_bindings({
            'time': 'time',
            'pace': 'pace',
            'diffusion_current': 'idiff',
        })

        # Reserve keywords
        model.reserve_unique_names(*keywords)
        model.reserve_unique_names(
            *['calc_' + c.name() for c in model.components()])
        model.reserve_unique_names(
            'cid',
            'dt',
            'g',
            'idiff',
            'idiff_vec'
            'n_cells',
            'offset',
            'pace',
            'pace_vec',
            'state',
            'time',
        )
        model.create_unique_names()

        # Return variables
        return {
            'model': model,
            'precision': myokit.SINGLE_PRECISION,
            'native_math': True,
            'bound_variables': bound_variables,
            'rl_states': rl_states,
        }
示例#4
0
    def test_inf_tau_form(self):
        # Test methods for working with inf-tau form
        m = myokit.parse_model(MODEL)
        self.assertIsInstance(m, myokit.Model)
        v = m.get('membrane.V')
        a = m.get('ikr.a')
        r = m.get('ikr.r')

        # Test with v detected from label
        self.assertTrue(hh.has_inf_tau_form(a))
        inf, tau = hh.get_inf_and_tau(a)
        self.assertEqual(inf, m.get('ikr.a.inf'))
        self.assertEqual(tau, m.get('ikr.a.tau'))
        self.assertFalse(hh.has_inf_tau_form(r))
        self.assertIsNone(hh.get_inf_and_tau(r))

        # Test with v argument, no label
        v.set_label(None)
        self.assertRaisesRegex(ValueError, 'Membrane potential must be given',
                               hh.has_inf_tau_form, a)
        self.assertTrue(hh.has_inf_tau_form(a, v))
        inf, tau = hh.get_inf_and_tau(a, v)
        self.assertEqual(inf, m.get('ikr.a.inf'))
        self.assertEqual(tau, m.get('ikr.a.tau'))
        self.assertFalse(hh.has_inf_tau_form(r, v))
        self.assertIsNone(hh.get_inf_and_tau(r, v))

        # Test with v as a constant
        v.demote()
        v.set_rhs(-80)
        self.assertTrue(hh.has_inf_tau_form(a, v))
        inf, tau = hh.get_inf_and_tau(a, v)
        self.assertEqual(inf, m.get('ikr.a.inf'))
        self.assertEqual(tau, m.get('ikr.a.tau'))
        self.assertFalse(hh.has_inf_tau_form(r, v))
        self.assertIsNone(hh.get_inf_and_tau(r, v))
        del (r)

        # a is not a state
        self.assertTrue(hh.has_inf_tau_form(a, v))
        a.demote()
        self.assertFalse(hh.has_inf_tau_form(a, v))
        a.promote(0)
        self.assertTrue(hh.has_inf_tau_form(a, v))

        # Almost correct forms / different ways to fail
        def bad(e):
            a.set_rhs(e)
            self.assertFalse(hh.has_inf_tau_form(a, v))
            a.set_rhs('(inf - a) / tau')
            self.assertTrue(hh.has_inf_tau_form(a, v))

        def good(e):
            a.set_rhs(e)
            self.assertTrue(hh.has_inf_tau_form(a, v))

        bad('(inf - a) / (1 + tau)')
        bad('(inf + a) / tau')
        bad('(inf - 1) / tau')
        bad('(1 - inf) / tau')
        bad('(inf - r) / tau')

        # Inf and tau can't be states
        m.get('ikr.a').move_variable(m.get('ikr.a.inf'), m.get('ikr'), 'ainf')
        m.get('ikr.a').move_variable(m.get('ikr.a.tau'), m.get('ikr'), 'atau')
        self.assertTrue(hh.has_inf_tau_form(a, v))
        m.get('ikr.ainf').promote(1)
        self.assertFalse(hh.has_inf_tau_form(a, v))
        m.get('ikr.ainf').demote()
        self.assertTrue(hh.has_inf_tau_form(a, v))
        m.get('ikr.atau').promote(1)
        self.assertFalse(hh.has_inf_tau_form(a, v))
        m.get('ikr.atau').demote()
        self.assertTrue(hh.has_inf_tau_form(a, v))

        # Inf and tau can't depend on other states than v
        c = m.add_component('ccc')
        x = c.add_variable('vvv')
        x.set_rhs(1)
        x.promote(0)
        ainf = m.get('ikr.ainf').rhs()
        m.get('ikr.ainf').set_rhs('2 + ccc.vvv * V')
        self.assertFalse(hh.has_inf_tau_form(a, v))
        m.get('ikr.ainf').set_rhs(ainf)
        self.assertTrue(hh.has_inf_tau_form(a, v))
        atau = m.get('ikr.atau').rhs()
        m.get('ikr.atau').set_rhs('3 * ccc.vvv * V')
        self.assertFalse(hh.has_inf_tau_form(a, v))
        m.get('ikr.atau').set_rhs(atau)
        self.assertTrue(hh.has_inf_tau_form(a, v))
    def __init__(self,
                 model,
                 protocol=None,
                 ncells=256,
                 diffusion=True,
                 precision=myokit.SINGLE_PRECISION,
                 native_maths=False,
                 rl=False):
        super(SimulationOpenCL, self).__init__()

        # Require a valid model
        model.validate()

        # Require independent components
        if model.has_interdependent_components():
            cycles = '\n'.join([
                '  ' + ' > '.join([x.name() for x in c])
                for c in model.component_cycles()
            ])
            raise ValueError(
                'This simulation requires models without interdependent'
                ' components. Please restructure the model and re-run.'
                '\nCycles:\n' + cycles)

        # Set protocol
        self.set_protocol(protocol)

        # Check dimensionality, number of cells
        try:
            if len(ncells) != 2:
                raise ValueError(
                    'The argument "ncells" must be either a scalar or a tuple'
                    ' (nx, ny).')
            self._nx = int(ncells[0])
            self._ny = int(ncells[1])
            self._dims = (self._nx, self._ny)
        except TypeError:
            self._nx = int(ncells)
            self._ny = 1
            self._dims = (self._nx, )
        if self._nx < 1 or self._ny < 1:
            raise ValueError(
                'The number of cells in any direction must be at least 1.')
        self._ntotal = self._nx * self._ny

        # Set diffusion mode
        self._diffusion_enabled = True if diffusion else False

        # Set precision
        if precision not in (myokit.SINGLE_PRECISION, myokit.DOUBLE_PRECISION):
            raise ValueError('Only single and double precision are supported.')
        self._precision = precision

        # Set native maths
        self._native_math = bool(native_maths)

        # Set rush-larsen mode
        self._rl = bool(rl)

        # Get membrane potential variable (from pre-cloned model!)
        vm = model.label('membrane_potential')
        if vm is None:
            raise ValueError(
                'This simulation requires the membrane potential'
                ' variable to be labelled as "membrane_potential".')
        if not vm.is_state():
            raise ValueError('The variable labelled as membrane potential must'
                             ' be a state variable.')

        #if vm.is_referenced():
        #  raise ValueError('This simulation requires that no other variables'
        #      ' depend on the time-derivative of the membrane potential.')

        # Prepare for Rush-Larsen updates, and/or clone model
        self._rl_states = {}
        if self._rl:
            import myokit.lib.hh as hh

            # Convert alpha-beta formulations to inf-tau forms, cloning model
            self._model = hh.convert_hh_states_to_inf_tau_form(model, vm)
            self._vm = self._model.get(vm.qname())
            del (model, vm)

            # Get (inf, tau) tuple for every Rush-Larsen state
            for state in self._model.states():
                res = hh.get_inf_and_tau(state, self._vm)
                if res is not None:
                    self._rl_states[state] = res

        else:
            # Clone model, store
            self._model = model.clone()
            self._vm = self._model.get(vm.qname())
            del (model, vm)

        # Set default conductance values
        self.set_conductance()

        # Set connections
        self._connections = None

        # Set default paced cells
        self._paced_cells = []
        if diffusion:
            self.set_paced_cells()
        else:
            self.set_paced_cells(self._nx, self._ny, 0, 0)

        # Scalar fields
        self._fields = OrderedDict()

        # Set default time step
        self.set_step_size()

        # Set initial time
        self._time = 0

        # Count number of states
        self._nstate = self._model.count_states()

        # Set state and default state
        self._state = self._model.state() * self._ntotal
        self._default_state = list(self._state)

        # List of globally logged inputs
        self._global = ['time', 'pace']

        # Process bindings: remove unsupported bindings, get map of bound
        # variables to internal names.
        inputs = {'time': 'time', 'pace': 'pace'}
        if self._diffusion_enabled:
            inputs['diffusion_current'] = 'idiff'
        self._bound_variables = self._model.prepare_bindings(inputs)

        # Reserve keywords
        from myokit.formats import opencl
        self._model.reserve_unique_names(*opencl.keywords)
        self._model.reserve_unique_names(
            *['calc_' + c.name() for c in self._model.components()])
        self._model.reserve_unique_names(
            *['D_' + c.uname() for c in self._model.states()])
        self._model.reserve_unique_names(*KEYWORDS)
        self._model.create_unique_names()

        # Create back-end
        SimulationOpenCL._index += 1
        mname = 'myokit_sim_opencl_' + str(SimulationOpenCL._index)
        fname = os.path.join(myokit.DIR_CFUNC, SOURCE_FILE)
        args = {
            'module_name': mname,
            'model': self._model,
            'precision': self._precision,
            'dims': len(self._dims),
        }

        # Debug
        if myokit.DEBUG:
            print(
                self._code(fname, args,
                           line_numbers=myokit.DEBUG_LINE_NUMBERS))
            #import sys
            #sys.exit(1)

        # Define libraries
        libs = []
        plat = platform.system()
        if plat != 'Darwin':  # pragma: no osx cover
            libs.append('OpenCL')
        if plat != 'Windows':  # pragma: no windows cover
            libs.append('m')

        # Create extension
        libd = list(myokit.OPENCL_LIB)
        incd = list(myokit.OPENCL_INC)
        incd.append(myokit.DIR_CFUNC)
        self._sim = self._compile(mname, fname, args, libs, libd, incd)