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)
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, }
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)