Nektar++
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
CellMLToNektarTranslator.py
Go to the documentation of this file.
1 
2 #Importing various functions used by this class (i have outcommented modules that we don't need)
3 import os
4 import optparse
5 import re
6 import time
7 import sys
8 from cStringIO import StringIO
9 
10 # Common CellML processing stuff below
11 import pycml
12 from pycml import * # Put contents in the local namespace as well
13 import optimize
14 import processors
15 import validator
16 
17 #Importing functions from modified_translators.py
18 import translators
19 #from modified_translators import CellMLTranslator
20 
22  """
23  As CellMLTranslator, but targets more recent Chaste style.
24 
25  Includes the ability to output a cell that can solve itself using
26  backward Euler, if the appropriate analyses have been done on the
27  model. (See the -J and -j options to translate.py.)
28  """
29 
30  # We want separate .cpp/.hpp files
31  USES_SUBSIDIARY_FILE = True
32 
33  # Type of (a reference to) the state variable vector
34  TYPE_VECTOR = 'std::vector<double> '
35  TYPE_VECTOR_REF = 'std::vector<double>& '
36 
37  NODESET = []
38 
39  def writeln_hpp(self, *args, **kwargs):
40  """Convenience wrapper for writing to the header file."""
41  kwargs['subsidiary'] = True
42  self.writeln(*args, **kwargs)
43 
44  def translate(self, *args, **kwargs):
45  """Generate code for the given model."""
46  our_kwargs = {'use_chaste_stimulus': False,
47  'separate_lut_class': True,
48  'convert_interfaces': False,
49  'use_modifiers': False,
50  'use_data_clamp': False,
51  'dynamically_loadable': False,
52  'use_protocol': False
53  }
54  for key, default in our_kwargs.iteritems():
55  setattr(self, key, kwargs.get(key, default))
56  if key in kwargs:
57  del kwargs[key]
58  # Some other default settings
59  self.use_backward_euler = False
60  self.include_serialization = False
61  # Last method's access specification
62  self._last_method_access = 'private'
63  return super(CellMLToNektarTranslator, self).translate(*args, **kwargs)
64 
66  """Set the LT method prefix (requires self.class_name to be set)."""
67  if self.separate_lut_class:
68  self.lt_class_name = self.class_name + '_LookupTables'
69  self.lookup_method_prefix = self.lt_class_name + '::Instance()->'
70  return super(CellMLToNektarTranslator, self).final_configuration_hook()
71 
72  def output_includes(self, base_class=None):
73  """Output the start of each output file.
74 
75  As well as the #include lines, it also outputs the include guard for
76  the .hpp file, and doxygen comment.
77 
78  If base_class is not None (and self.use_backward_euler isn't set)
79  then includes that class' header instead of AbstractCardiacCell.
80 
81  If self.dynamically_loadable is set, includes extra headers needed
82  for that case.
83 
84  Reads self.include_serialization and self.use_backward_euler.
85  Sets self.base_class_name and self.class_inheritance.
86  """
87 
88 
89  from translators import version_comment
90  for sub in [False, True]:
91  self.output_doxygen('@file\n\n',
92  'This source file was generated from CellML.\n\n',
93  'Model: ', self.model.name, '\n\n',
95  '\n\n<autogenerated>',
96  subsidiary=sub)
97  self.writeln(subsidiary=sub)
98 
99 
100  self.writeln('#include <iostream>')
101  self.writeln('#include <string>')
102 
103  # .cpp should include .hpp
104  #writes path
105  self.writeln('#include <CardiacEPSolver/CellModels/', os.path.basename(self.subsidiary_filename), '>')
106  # if self.include_serialization:
107  # self.writeln_hpp('#include "ChasteSerialization.hpp"')
108  # self.writeln_hpp('#include <boost/serialization/base_object.hpp>')
109  #self.writeln('#include <cmath>')
110  #self.writeln('#include <cassert>')
111  #self.writeln('#include <memory>')
112 
113  #write the namespace (and the open bracket)
114  self.writeln()
115  self.writeln('namespace Nektar')
116  self.writeln('{')
117 
118 
119  if self.use_backward_euler:
120  self.writeln_hpp('#include "AbstractBackwardEulerCardiacCell.hpp"')
121  self.writeln('#include "CardiacNewtonSolver.hpp"')
122  self.base_class_name = 'AbstractBackwardEulerCardiacCell<' + \
123  str(self.nonlinear_system_size) + '>'
124  elif self.options.rush_larsen:
125  self.base_class_name = 'AbstractRushLarsenCardiacCell'
126  self.writeln_hpp('#include "' + self.base_class_name + '.hpp"')
127  if not self.doc._cml_rush_larsen:
128  self.writeln('#include "Warnings.hpp"')
129  elif self.options.grl1:
130  self.base_class_name = 'AbstractGeneralizedRushLarsenCardiacCell'
131  self.writeln_hpp('#include "' + self.base_class_name + '.hpp"')
132  elif self.options.grl2: #1992 TODO: merge with above case
133  self.base_class_name = 'AbstractGeneralizedRushLarsenCardiacCell'
134  self.writeln_hpp('#include "' + self.base_class_name + '.hpp"')
135  elif base_class:
136  self.base_class_name = base_class
137  self.writeln_hpp('#include "' + self.base_class_name + '.hpp"')
138  else:
139  self.base_class_name = 'AbstractCardiacCell'
140  # self.writeln_hpp('#include "' + self.base_class_name + '.hpp"')
141  if self.use_modifiers:
142  self.writeln_hpp('#include "AbstractCardiacCellWithModifiers.hpp"')
143  self.writeln_hpp('#include "AbstractModifier.hpp"')
144  # Modify the base class name
145  self.base_class_name = 'AbstractCardiacCellWithModifiers<' + self.base_class_name + ' >'
146  self.class_inheritance = ' : public CellModel'# + self.base_class_name
147  if self.dynamically_loadable:
148  self.writeln_hpp('#include "AbstractDynamicallyLoadableEntity.hpp"')
149  self.class_inheritance += ', public AbstractDynamicallyLoadableEntity'
150  if self.use_protocol:
151  self.writeln_hpp('#include "AbstractTemplatedSystemWithOutputs.hpp"')
152  self.class_inheritance += ', public AbstractTemplatedSystemWithOutputs<' + self.TYPE_VECTOR + '>'
153  #self.writeln('#include "Exception.hpp"')
154  #self.writeln('#include "OdeSystemInformation.hpp"')
155  #self.writeln('#include "RegularStimulus.hpp"')
156  # self.writeln_hpp('#include "AbstractStimulusFunction.hpp"')
157  #self.writeln('#include "HeartConfig.hpp"')
158  #self.writeln('#include "IsNan.hpp"')
159  #self.writeln('#include "MathsCustomFunctions.hpp"')
160  #self.writeln()
161 
162  self.writeln_hpp('#ifndef NEKTAR_SOLVERS_ADRSOLVER_EQUATIONSYSTEMS_', self.include_guard)
163  self.writeln_hpp('#define NEKTAR_SOLVERS_ADRSOLVER_EQUATIONSYSTEMS_', self.include_guard, '\n')
164 
165  self.writeln_hpp('#include <CardiacEPSolver/CellModels/CellModel.h>')
166  self.writeln_hpp('namespace Nektar')
167 
168  self.writeln_hpp()
169 
170  def set_access(self, access):
171  """Set the access specification for subsequent output.
172 
173  We keep track of the last access set, either via this method or
174  output_method_start, and only output a new declaration to the
175  header file if it changes.
176  """
177  if access != self._last_method_access:
178  self._last_method_access = access
179  self.writeln_hpp()
180  # self.writeln_hpp(access, ':', indent_offset=-1)
181 
182  def output_method_start(self, method_name, args, ret_type, access=None, defaults=[]):
183  """Output the start of a method declaration/definition.
184 
185  Will write to both the .hpp and .cpp file.
186 
187  We keep track of the access of the last method, and only output a new
188  declaration to the header file if it changes. The default is to use
189  the same access specification as last time.
190  """
191  DEBUG('translator', 'Generating code for method', method_name)
192  if access:
193  self.set_access(access)
194  if ret_type:
195  if ret_type[-1] != ' ':
196  ret_type = ret_type + ' '
197  else:
198  ret_type = ''
199  args_string_cpp = ', '.join(filter(None, map(str, args)))
200  if defaults:
201  assert len(defaults) == len(args)
202  args_with_default = []
203  for (arg, default) in zip(map(str, args), map(str, defaults)):
204  if arg:
205  if default:
206  args_with_default.append(arg + '=' + default)
207  else:
208  args_with_default.append(arg)
209  args_string_hpp = ', '.join(args_with_default)
210  else:
211  args_string_hpp = args_string_cpp
212  self.writeln_hpp(ret_type, method_name, '(', args_string_hpp, ')', self.STMT_END)
213  self.writeln(ret_type, self.class_name, '::', method_name, '(', args_string_cpp, ')')
214 
216  """Output a ComputeDerivedQuantities method if any such quantities exist.
217 
218  Looks for variables annotated with pycml:derived-quantity=yes, and generates
219  a method to compute all these variables from a given state.
220  """
221  dqs = self.derived_quantities
222  if dqs:
223  self.output_method_start('ComputeDerivedQuantities',
224  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
225  'const ' + self.TYPE_VECTOR + '& rY'], # We need it to really be a reference
226  self.TYPE_VECTOR, access='public')
227  self.open_block()
228  self.output_comment('Inputs:')
229  self.output_comment('Time units: ', self.free_vars[0].units)
230  # Work out what equations are needed
231  if self.use_chaste_stimulus:
232  i_stim = [self.doc._cml_config.i_stim_var]
233  else:
234  i_stim = []
235  if self.use_data_clamp:
236  prune = [self.config.i_data_clamp_data]
237  else:
238  prune = []
239  nodeset = self.calculate_extended_dependencies(dqs, prune_deps=i_stim, prune=prune)
240 
241 
242  # State variable inputs
243  self.output_state_assignments(assign_rY=False, nodeset=nodeset)
244  self.writeln()
245  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset, self.code_name(self.free_vars[0]))
246  # Output equations
247  self.output_comment('Mathematics')
248  self.output_equations(nodeset - table_index_nodes_used)
249  self.writeln()
250  # Assign to results vector
251  self.writeln(self.vector_create('dqs', len(dqs)))
252  for i, var in enumerate(dqs):
253  self.writeln(self.vector_index('dqs', i), self.EQ_ASSIGN, self.code_name(var), self.STMT_END)
254  self.writeln('return dqs', self.STMT_END)
255  self.close_block(blank_line=True)
256 
258  """This method outputs the boost serialize method for the
259  header files that need it."""
260  # Serialization
261  if self.include_serialization:
262  # self.writeln_hpp('friend class boost::serialization::access;')
263  self.writeln_hpp('public:')
264  # self.writeln_hpp('template<class Archive>')
265  self.writeln_hpp('/// Creates an instance of this class', indent_offset=1)
266  # self.writeln_hpp('void serialize(Archive & archive, const unsigned int version)')
267  self.writeln_hpp('static CellModelSharedPtr create(', indent_offset=1)
268  self.writeln_hpp('const LibUtilities::SessionReaderSharedPtr& pSession,', indent_offset=3)
269  self.writeln_hpp('const MultiRegions::ExpListSharedPtr& pField)', indent_offset=3)
270  self.open_block(subsidiary=True,indent_offset=1)
271  self.writeln_hpp('return MemoryManager<',self.class_name,'>::AllocateSharedPtr(pSession, pField);',indent_offset=1)
272  # self.writeln_hpp('archive & boost::serialization::base_object<', self.base_class_name,
273  # ' >(*this);')
274  if self.dynamically_loadable:
275  self.writeln_hpp('archive & boost::serialization::base_object<AbstractDynamicallyLoadableEntity>(*this);')
276  if self.use_modifiers:
277  self.output_comment('Despite this class having modifier member variables, they are all added to the', subsidiary=True)
278  self.output_comment('abstract class by the constructor, and archived via that, instead of here.', subsidiary=True)
279  self.close_block(subsidiary=True,indent_offset=1)
280 
281  self.writeln_hpp('/// Name of class',indent_offset=1)
282  self.writeln_hpp('static std::string className;\n',indent_offset=1)
283 
284 
285  class_name_length = len(self.class_name)+1
286  self.writeln_hpp('/// Constructor',indent_offset=1)
287  self.writeln_hpp(self.class_name,'(const LibUtilities::SessionReaderSharedPtr& pSession,',indent_offset=1)
288  self.writeln_hpp(' '*class_name_length, 'const MultiRegions::ExpListSharedPtr& pField);\n',indent_offset=1)
289 
290  self.writeln_hpp('/// Destructor',indent_offset=1)
291  self.writeln_hpp('virtual ~',self.class_name,'() {}\n',indent_offset=1)
292 
293  self.writeln_hpp('protected:')
294  self.writeln_hpp('/// Computes the reaction terms $f(u,v)$ and $g(u,v)$.',indent_offset=1)
295  self.writeln_hpp('virtual void v_Update(',indent_offset=1)
296  self.writeln_hpp('const Array<OneD, const Array<OneD, NekDouble> >&inarray,',indent_offset=3)
297  self.writeln_hpp(' Array<OneD, Array<OneD, NekDouble> >&outarray,',indent_offset=3)
298  self.writeln_hpp('const NekDouble var_chaste_interface__environment__time);\n',indent_offset=3)
299 
300  self.writeln_hpp('/// Prints a summary of the model parameters.',indent_offset=1)
301  self.writeln_hpp('virtual void v_GenerateSummary(SummaryList& s);\n',indent_offset=1)
302 
303  self.writeln_hpp('/// Set initial conditions for the cell model',indent_offset=1)
304  self.writeln_hpp('virtual void v_SetInitialConditions();',indent_offset=1)
305  self.writeln_hpp('};')
306  self.close_block(subsidiary=True)
307  self.writeln_hpp('#endif')
308 
310  """Output declarations, set & get methods for cell parameters.
311 
312  Sets self.cell_parameters to be those constant variables annotated with
313  pycml:modifiable-parameter. These use the mParameters functionality in
314  Chaste.
315 
316  Also collects any variables annotated with an RDF oxmeta name into
317  self.metadata_vars. Only constants and state variables are included.
318  """
319  # Find annotated parameters
320  self.cell_parameters = filter(
321  lambda v: v.is_modifiable_parameter,
322  cellml_metadata.find_variables(self.model,
323  ('pycml:modifiable-parameter', NSS['pycml']),
324  'yes'))
325 
326  # Reduce intra-run variation
327  self.cell_parameters.sort(key=self.var_display_name)
328 
329  for i, var in enumerate(self.cell_parameters):
330  # Remember the var's index
331  var._cml_param_index = i
332 
333  # Create set of all oxmeta-annotated variables
334  vars = cellml_metadata.find_variables(self.model, ('bqbiol:is', NSS[u'bqbiol']))
335  # Keep only the variables with an oxmeta name
336  vars = filter(lambda v: v.oxmeta_name, vars)
337  # We're interested in anything that isn't time or the stimulus
338  self.metadata_vars = set([v for v in vars if v.get_type() != VarTypes.Free])
339  self.metadata_vars.discard(self.doc._cml_config.i_stim_var)
340  self.metadata_vars = list(self.metadata_vars)
341  self.metadata_vars.sort(key=self.var_display_name)
342 
343  # #1464 Create a set of metadata variables that will have modifiers
344  # We want to avoid writing out metadata for stimulus current as it is used once and then discarded.
345  # \todo - use protocol information to put only the required modifiers into this list.
346  self.modifier_vars = [v for v in self.metadata_vars if v.oxmeta_name not in cellml_metadata.STIMULUS_NAMES and v.oxmeta_name != 'membrane_capacitance']
347  self.modifier_vars.sort(key=self.var_display_name)
348 
349  # Generate member variable declarations
350  self.set_access('private')
351  if self.metadata_vars:
352  self.output_comment('\nSettable parameters and readable variables\n', subsidiary=True)
353 
354  # Write out the modifier member variables.
355  if self.use_modifiers:
356  for var in self.modifier_vars:
357  self.writeln_hpp('boost::shared_ptr<AbstractModifier> mp_' + var.oxmeta_name + '_modifier', self.STMT_END)
358 
359  # Methods associated with oxmeta annotated variables
360  # Don't use LT & modifiers for the const methods
361  use_modifiers = self.use_modifiers
362  self.use_modifiers = False
363  use_lt = self.use_lookup_tables
364  self.use_lookup_tables = False
365  for var in self.metadata_vars:
366  if var.is_statically_const(ignore_annotations=True):
367 # self.output_method_start('Get_' + var.oxmeta_name + '_constant', [], self.TYPE_DOUBLE)
368 # self.open_block()
369 # self.output_comment('Constant value given in CellML')
370 # nodeset = self.calculate_extended_dependencies([var])
371 # self.output_equations(nodeset)
372 # self.writeln('return ', self.code_name(var), self.STMT_END)
373 # self.close_block()
374 # self.writeln()
375  if var in self.cell_parameters and var in self.modifier_vars:
376  # 'Forget' its index, so normal code generation occurs (#1647)
377  var._cml_has_modifier = True
378  self.use_lookup_tables = use_lt
379  self.use_modifiers = use_modifiers
382 
383  # Find & store derived quantities, for use elsewhere
384  self.derived_quantities = cellml_metadata.find_variables(self.model,
385  ('pycml:derived-quantity', NSS['pycml']),
386  'yes')
387  # Reduce intra-run variation
388  self.derived_quantities.sort(key=self.var_display_name)
389 
391  """
392  Output a default cell stimulus from the metadata specification
393  as long as the following metadata exists:
394  * membrane_stimulus_current_amplitude
395  * membrane_stimulus_current_duration
396  * membrane_stimulus_current_period
397  and optionally:
398  * membrane_stimulus_current_offset
399  * membrane_stimulus_current_end
400 
401  Ensures that the amplitude of the generated RegularStimulus is negative.
402  """
403  vars = dict()
404  for n in ['duration', 'amplitude', 'period', 'offset', 'end']:
405  vars[n] = self.model.get_variable_by_oxmeta_name('membrane_stimulus_current_'+n, throw=False)
406  if not (vars['duration'] and vars['amplitude'] and vars['period']):
407  self.has_default_stimulus = False
408  return
409  self.has_default_stimulus = True
410  nodeset = self.calculate_extended_dependencies(filter(None, vars.values()))
411 
412  self.output_method_start('UseCellMLDefaultStimulus', [], 'boost::shared_ptr<RegularStimulus>', 'public')
413  self.open_block()
414  self.output_comment('Use the default stimulus specified by CellML metadata')
415  self.output_equations(nodeset)
416  self.writeln('boost::shared_ptr<RegularStimulus> p_cellml_stim(new RegularStimulus(')
417  self.writeln(' -fabs(', self.code_name(vars['amplitude']), '),')
418  self.writeln(' ', self.code_name(vars['duration']), ',')
419  self.writeln(' ', self.code_name(vars['period']), ',')
420  if vars['offset']:
421  self.writeln(' ', self.code_name(vars['offset']))
422  else:
423  self.writeln(' 0.0')
424  if vars['end']:
425  self.writeln(' , ', self.code_name(vars['end']))
426  self.writeln(' ))', self.STMT_END)
427  self.writeln('mpIntracellularStimulus = p_cellml_stim', self.STMT_END)
428  self.writeln('return p_cellml_stim', self.STMT_END)
429  self.close_block(blank_line=True)
430 
432  """
433  If a (state) variable has been annotated as cytosolic_calcium_concentration,
434  generate a GetIntracellularCalciumConcentration method.
435  """
436  # Find cytosolic_calcium_concentration
437  cai = self.doc.model.get_variable_by_oxmeta_name('cytosolic_calcium_concentration', throw=False)
438  if cai and cai in self.state_vars:
439  i = self.state_vars.index(cai[0])
440  self.output_method_start('GetIntracellularCalciumConcentration', [], self.TYPE_DOUBLE, 'public')
441  self.open_block()
442  self.writeln('return ', self.vector_index('mStateVariables', i), self.STMT_END)
443  self.close_block(blank_line=True)
444 
445  def code_name(self, var, *args, **kwargs):
446  """
447  Return the full name of var in a form suitable for inclusion in a source file.
448 
449  Overrides the base class version to access mParameters for parameters.
450  """
451  #self.writeln(var)
452  if hasattr(var, '_cml_param_index') and not (self.use_modifiers and getattr(var, '_cml_has_modifier', False)):
453  return self.vector_index('mParameters', var._cml_param_index)
454  elif var is getattr(self.model, u'_cml_Chaste_Cm', None):
455  return 'HeartConfig::Instance()->GetCapacitance()'
456  elif hasattr(var, '_cml_code_name'):
457  return var._cml_code_name % {'time': self.code_name(self.free_vars[0])}
458  else:
459  # self.writeln(var)
460  # self.writeln(super(CellMLToNektarTranslator, self).code_name(var, *args, **kwargs))
461  return super(CellMLToNektarTranslator, self).code_name(var, *args, **kwargs)
462 
463 
465  """Output top boilerplate.
466 
467  This method outputs the constructor and destructor of the cell
468  class, and also lookup table declarations and lookup methods.
469  It also calls output_verify_state_variables.
470  """
471  self.include_serialization = True
472 
473  # Check if we're generating a Backward Euler model
474  self.use_backward_euler = self.model.get_option('backward_euler')
475  self.use_analytic_jacobian = (self.model.get_option('maple_output') and hasattr(self.model.solver_info, u'jacobian'))
476  if self.use_backward_euler:
477  assert hasattr(self.model, u'solver_info')
478  # Find the size of the nonlinear system
479  num_linear_odes = len(self.model.solver_info.xml_xpath(u'solver:linear_odes/m:math/m:apply'))
480  self.nonlinear_system_size = len(self.state_vars) - 1 - num_linear_odes
481  nonlinear_entries = self.model.solver_info.xml_xpath(u'solver:jacobian/solver:entry/@var_j')
482  self.nonlinear_system_vars = map(self.varobj, nonlinear_entries[:self.nonlinear_system_size])
483  # Start output
484  self.output_includes()
485 
486  if self.use_backward_euler or self.options.rush_larsen or self.options.grl1 or self.options.grl2:
487  # Keep the same signature as forward cell models, but note that the solver isn't used
488  solver1 = 'boost::shared_ptr<AbstractIvpOdeSolver> /* unused; should be empty */'
489  solver2 = ''
490  #solver1 = solver2 = ''
491  else: #this currently outputs the boilerplate stuff
492  solver1 = 'boost::shared_ptrALPHA<AbstractIvpOdeSolver> pSolverBRAVO'
493  solver2 = '"' + self.class_name + '"'
494 
495  if self.use_lookup_tables and self.separate_lut_class:
496  self.output_lut_class()
497 
498  # Cell model class
499  self.open_block(subsidiary=True)
500  self.writeln_hpp('class ', self.class_name, self.class_inheritance)
501  self.writeln_hpp('{\n')
502 
503  # Put the boost serialize() method in if requested.
505 
506  # Parameter declarations, and set & get methods (#666)
508  # Constructor
509  self.set_access('public')
510 
511  self.output_constructor([solver1, 'boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus'],
512  [solver2, self.class_name + '::create','"Description of the model?"'])
513  # Destructor
514  #self.output_method_start(''+self.class_name, [], '')
515  self.writeln(self.class_name + '::' + self.class_name + '(')
516  self.writeln('const LibUtilities::SessionReaderSharedPtr& pSession,' , indent_offset=3)
517  self.writeln('const MultiRegions::ExpListSharedPtr& pField):', indent_offset=3)
518  self.writeln('CellModel(pSession, pField)', indent_offset=2)
519 
520 
521  self.open_block()
522 
523  #writing out all the state variables as a comment
524  self.writeln('/*')
525  self.writeln('State Variables:')
526  for var in self.state_vars:
527  self.writeln(var)
528  self.writeln()
529  self.writeln('Free Variables:')
530  for var in self.free_vars:
531  self.writeln(var)
532  self.writeln()
533  # for var in self.used_vars:
534  # self.writeln(var)
535  self.writeln('*/\n')
536 
537  #outputting the number of variables in the state_vars list
538  self.writeln('m_nq = pField->GetNpoints();\n')
539  self.writeln('m_nvar = ', len(self.state_vars), ';')
540 
541 
542 
543 
544 
545  #create a list of tau and infs, if present
546  #setting up the arrays to contain the taus, infs, alphas and betas
547  self.taus = []
548  self.infs = []
549  self.alphas = []
550  self.betas = []
551 
552  # creating the nodeset that contains the mathematics
553  state_vars = self.doc.model.find_state_vars()
554  derivs = set(map(lambda v: (v, self.free_vars[0]), state_vars))
555  extra_nodes=set()
556  i_stim = []
557 
558  nonv_nodeset = self.calculate_extended_dependencies(derivs|extra_nodes, prune_deps=i_stim)
559  prune = nonv_nodeset
560  dvdt = (self.v_variable, self.free_vars[0])
561  v_nodeset = self.calculate_extended_dependencies([dvdt], prune=prune, prune_deps=i_stim)
562  all_nodes = nonv_nodeset|v_nodeset
563  extra_table_nodes=set()
564  table_index_nodes_used = self.calculate_lookup_table_indices(all_nodes|extra_table_nodes, self.code_name(self.free_vars[0]))
565  self.NODESET = nonv_nodeset - table_index_nodes_used
566 
567  #searching through the nodeset for all the infs, taus, alphas and betas and adding them to their arrays
568  for expr in (e for e in self.model.get_assignments() if e in self.NODESET):
569  # self.writeln('test')
570  if isinstance(expr, cellml_variable):
571  codename = str(self.code_name(expr))
572  # self.writeln(self.code_name(expr))
573  if 'gate' in codename:
574  if '__tau_'in codename:
575  self.taus.append(codename)
576  if '_inf' in codename:
577  self.infs.append(codename)
578  if '_alpha' in codename:
579  self.alphas.append(codename)
580  if '_beta' in codename:
581  self.betas.append(codename)
582 
583  # debugging help to print the arrays
584  # self.writeln('Taus: ' + str(taus))
585  # self.writeln('Infs: ' + str(infs))
586  # self.writeln('Alphas: ' + str(self.alphas))
587  # self.writeln('Betas: ' + str(self.betas) + '\n')
588 
589  #initialising the gate_vars and concentration_vars counters
590  gate_vars = 0
591  concentration_vars = 0
592  self.state_var_type = {}
593 
594  #finding the actual variable name for each of the state variables
595  for var in self.state_vars:
596  # self.writeln()
597  # self.writeln(var)
598  var_actual = str(var)[33:str(var).rfind('__')]
599  # self.writeln(var_actual)
600 
601 
602  #writing the gate and concentration variables
603  if str(var).find('membrane__V') != -1:
604  self.state_var_type[str(var)] = 'voltage'
605  elif filter(lambda element: var_actual in element,self.infs) and filter(lambda element: var_actual in element,self.taus) or filter(lambda element: var_actual in element,self.alphas) and filter(lambda element: var_actual in element,self.betas):
606  gate_vars += 1
607  # self.writeln(var, ' gating variable')
608  self.writeln('m_gates.push_back(',str(gate_vars),');')
609  self.state_var_type[str(var)] = 'm_gates'
610  # self.writeln()
611  else:
612  gate_vars += 1
613  # self.writeln(var, ' concentration variable')
614  self.writeln('m_concentrations.push_back(',str(gate_vars),');')
615  self.state_var_type[str(var)] = 'm_concentrations'
616  # self.writeln()
617 
618  # self.writeln(self.state_var_type)
619 
620  self.close_block()
621 
622 
623 
624 
625 
626 
627  # Other declarations & methods
630  return
631 
632  @property
633  def unsigned_v_index(self):
634  if self.v_index == -1:
635  return 'UNSIGNED_UNSET'
636  else:
637  return str(self.v_index)
638 
640  """Output the VerifyStateVariables method.
641 
642  This will look for state variables annotated with pycml:range-low and/or pycml:range-high,
643  which specify allowable ranges for these variables. The generated method will check that
644  they are within the range. Both limits are included, i.e. they specify a closed interval.
645  """
646 
647  # First work out if there are any constraints on state variables
648 
649  low_prop = ('pycml:range-low', NSS['pycml'])
650  high_prop = ('pycml:range-high', NSS['pycml'])
651  low_range_vars = filter(
652  lambda v: v.get_type() == VarTypes.State,
653  cellml_metadata.find_variables(self.model, low_prop))
654  high_range_vars = filter(
655  lambda v: v.get_type() == VarTypes.State,
656  cellml_metadata.find_variables(self.model, high_prop))
657  nodeset = set(low_range_vars + high_range_vars)
658 
659  # If not, don't bother writing the method, an empty implementation is in the abstract classes.
660  if nodeset:
661  # It's not appropriate to apply modifiers here - we want to check the actual values of the state
662  use_modifiers = self.use_modifiers
663  self.use_modifiers = False
664 
665  self.output_method_start('VerifyStateVariables', [], 'void')
666  self.open_block()
667 
668  using_cvode = (self.TYPE_VECTOR_REF == CellMLToCvodeTranslator.TYPE_VECTOR_REF)
669  if using_cvode:
670  self.writeln('/* We only expect CVODE to keep state variables to within its tolerances,')
671  self.writeln(' * not exactly the bounds prescribed to each variable that are checked here.')
672  self.writeln(' *')
673  self.writeln(' * For 99.99% of paces this->mAbsTol works,')
674  self.writeln(' * For 99.999% of paces 10*this->mAbsTol is fine,')
675  self.writeln(' * but unfortunately 100x seems to be required on rare occasions for upstrokes.')
676  self.writeln(' * This sounds bad, but is probably typically only 1e-5 or 1e-6.')
677  self.writeln(' */')
678  self.writeln('const double tol = 100*this->mAbsTol;')
679 
680  self.output_state_assignments(nodeset=nodeset)
681  error_template = 'EXCEPTION(DumpState("State variable {0} has gone out of range. Check numerical parameters, for example time and space stepsizes, and/or solver tolerances"));'
682  additional_tolerance_adjustment = ''
683  for var in low_range_vars:
684  if using_cvode:
685  additional_tolerance_adjustment = ' - tol'
686  self.writeln('if (', self.code_name(var), ' < ', var.get_rdf_annotation(low_prop), additional_tolerance_adjustment, ')')
687  self.open_block()
688  #self.writeln('std::cout << "Too small: ', self.code_name(var), ' = " << ', self.code_name(var) , ' << std::endl << std::flush;')
689  self.writeln(error_template.format(self.var_display_name(var)))
690  self.close_block(False)
691  for var in high_range_vars:
692  if using_cvode:
693  additional_tolerance_adjustment = ' + tol'
694  self.writeln('if (', self.code_name(var), ' > ', var.get_rdf_annotation(high_prop), additional_tolerance_adjustment, ')')
695  self.open_block()
696  #self.writeln('std::cout << "Too large: ', self.code_name(var), ' = " << ', self.code_name(var) , ' << std::endl << std::flush;')
697  self.writeln(error_template.format(self.var_display_name(var)))
698  self.close_block(False)
699  self.close_block(True)
700 
701  self.use_modifiers = use_modifiers
702 
703 
704  def output_constructor(self, params, base_class_params):
705  """Output a cell constructor.
706 
707  params is a list of constructor parameters, entries of which should be strings
708  including both type and parameter name, which will be included verbatim in the
709  generated code.
710 
711  base_class_params is a list of parameters to be supplied to the base class
712  constructor. Entries will be converted to strings.
713  """
714  #self.output_method_start(self.class_name, params, '', access='public')
715  self.writeln('std::string ', self.class_name, '::className')
716  self.writeln('= GetCellModelFactory().RegisterCreatorFunction(', indent_offset=2)
717  #self.writeln(' : ', self.base_class_name, '(')
718 
719  # Filter out empty params, to make backward Euler happy
720  base_class_params = filter(None, map(str, base_class_params))
721  for i, param in enumerate(base_class_params):
722  if i == len(base_class_params)-1: comma = ');'
723  else: comma = ','
724  self.writeln(param, comma, indent_offset=3)
725 
726  #self.open_block()
727  self.writeln()
728  self.writeln('/**')
729  self.writeln('*')
730  self.writeln('*/')
731  # self.writeln('this->mpSystemInfo = OdeSystemInformation<',
732  # self.class_name, '>::Instance();')
733  # if self.v_index == -1 and self.v_variable:
734  # self.writeln('this->mVoltageIndex = GetAnyVariableIndex("',
735  # self.var_display_name(self.v_variable), '");')
736  # if self.config.options.include_dt_in_tables:
737  # self.writeln(self.lt_class_name, '::Instance()->SetTimestep(mDt);')
738  # self.writeln('Init();\n')
739 
740  #1861 - Rush-Larsen
741  if self.options.rush_larsen and not self.doc._cml_rush_larsen:
742  self.writeln('WARNING("No elligible gating variables found for this Rush-Larsen cell model; using normal forward Euler.");')
743 
744  #1463 - default cellML stimulus
745  if self.has_default_stimulus:
746  self.output_comment('We have a default stimulus specified in the CellML file metadata')
747  self.writeln('this->mHasDefaultStimulusFromCellML = true', self.STMT_END)
748 
749  #1464 - cleverer modifiers...
750  if self.use_modifiers and self.modifier_vars:
751  self.output_comment('These will get initialised to DummyModifiers in the base class method.')
752  for var in self.modifier_vars:
753  self.writeln('this->AddModifier("' + var.oxmeta_name + '",')
754  self.writeln(' mp_' + var.oxmeta_name + '_modifier)', self.STMT_END)
755 
756  #666 - initialise parameters
757  for var in self.cell_parameters:
758  if var.get_type() == VarTypes.Constant:
759  self.writeln(self.vector_index('this->mParameters', var._cml_param_index),
760  self.EQ_ASSIGN, var.initial_value, self.STMT_END, ' ',
761  self.COMMENT_START, var.fullname(), ' [', var.units, ']')
762  #1354 - specify protocol outputs
763 
764  if self.use_protocol:
765  outputs = cellml_metadata.find_variables(self.model,
766  ('pycml:output-variable', NSS['pycml']),
767  'yes')
768  def write_output_info(output):
769  if output.get_type() in [VarTypes.Free, VarTypes.Unknown]:
770  self.writeln('UNSIGNED_UNSET, FREE', indent=False, nl=False)
771  elif output.get_type() == VarTypes.State:
772  self.writeln(self.state_vars.index(output), ', STATE', indent=False, nl=False)
773  elif output.is_derived_quantity:
774  self.writeln(self.derived_quantities.index(output), ', DERIVED', indent=False, nl=False)
775  elif output.is_modifiable_parameter:
776  self.writeln(self.cell_parameters.index(output), ', PARAMETER', indent=False, nl=False)
777  else:
778  raise ValueError('Unexpected protocol output: ' + str(output))
779  if outputs:
780  outputs.sort(key=lambda v: self.var_display_name(v))
781  self.output_comment('Protocol outputs')
782  self.writeln('this->mOutputsInfo.resize(', len(outputs), ');')
783  for i, output in enumerate(outputs):
784  self.writeln('this->mOutputsInfo[', i, ']', self.EQ_ASSIGN,
785  'std::make_pair(', nl=False)
786  write_output_info(output)
787  self.writeln(')', self.STMT_END, indent=False)
788  self.writeln()
789  outputs = set(outputs)
790  #1925 - outputs that are vectors
791  prop = ('pycml:output-vector', NSS['pycml'])
792  vector_names = set(cellml_metadata.get_targets(self.model, None,
793  cellml_metadata.create_rdf_node(prop)))
794  self.writeln('this->mVectorOutputsInfo.resize(', len(vector_names), ');')
795  self.writeln('this->mVectorOutputNames.resize(', len(vector_names), ');')
796  for i, name in enumerate(sorted(vector_names)):
797  self.writeln('this->mVectorOutputNames[', i, ']', self.EQ_ASSIGN, '"', name, '"', self.STMT_END)
798  vector_outputs = cellml_metadata.find_variables(self.model, prop, name)
799  assert len(vector_outputs) > 0
800  vector_outputs.sort(key=lambda v: self.var_display_name(v))
801  self.writeln('this->mVectorOutputsInfo[', i, '].resize(', len(vector_outputs), ');')
802  for j, output in enumerate(vector_outputs):
803  self.writeln('this->mVectorOutputsInfo[', i, '][', j, ']', self.EQ_ASSIGN,
804  'std::make_pair(', nl=False)
805  write_output_info(output)
806  self.writeln(')', self.STMT_END, indent=False)
807  self.writeln()
808  outputs.update(vector_outputs)
809  #1910 - SED-ML name mappings
810  prop = ('pycml:alias', NSS['pycml'])
811  aliased_vars = cellml_metadata.find_variables(self.model, prop, None)
812  prop = cellml_metadata.create_rdf_node(prop)
813  for var in aliased_vars:
814  assert var in outputs
815  source = cellml_metadata.create_rdf_node(fragment_id=var.cmeta_id)
816  for alias in cellml_metadata.get_targets(self.model, source, prop):
817  name = self.var_display_name(var)
818  self.writeln('this->mNameMap["', alias, '"] = "', name, '";')
819  #2178 - set up model outputs environment from above info
820  self.writeln()
821  self.writeln('ProcessOutputsInfo();')
822  self.writeln()
823  #2428 - also record protocol inputs
824  inputs = cellml_metadata.find_variables(self.model, ('pycml:input-variable', NSS['pycml']), 'yes')
825  if inputs:
826  inputs.sort(key=lambda v: self.var_display_name(v))
827  self.writeln('this->mInputNames.reserve(', len(inputs), ');')
828  for input in inputs:
829  self.writeln('this->mInputNames.push_back("', self.var_display_name(input), '");')
830 
831  # Lookup table generation, if not in a singleton
832  if self.use_lookup_tables and not self.separate_lut_class:
833  self.output_lut_generation()
835  #self.close_block()
836 
837  return
838 
840  """Hook for subclasses to add further content to the constructor."""
841  pass
842 
844  """
845  Output lookup table declarations & methods, if not using a separate class,
846  or output the method to get a pointer to the lookup table collection.
847  """
848  if self.use_lookup_tables:
849  if self.separate_lut_class:
850  self.output_method_start('GetLookupTableCollection', [], 'AbstractLookupTableCollection*')
851  self.open_block()
852  self.writeln('return ', self.lt_class_name, '::Instance();')
853  self.close_block()
854  else:
855  self.send_main_output_to_subsidiary()
856  self.output_lut_declarations()
857  self.output_lut_row_lookup_memory()
858  self.output_lut_methods()
859  self.send_main_output_to_subsidiary(False)
860 
861  def lut_parameters(self, key):
862  """Get the bounds and step size for a particular table.
863 
864  key should be a key into self.lookup_table_indices.
865  Returns (min, max, step) suitable for putting in generated code.
866  """
867  if self.separate_lut_class:
868  idx = self.doc.lookup_table_indexes[key]
869  return map(lambda s: 'mTable%s[%s]' % (s, idx), ['Mins', 'Maxs', 'Steps', 'StepInverses'])
870  else:
871  return super(CellMLToNektarTranslator, self).lut_parameters(key)
872 
874  """Output methods in the LT class for indexing the tables, and checking index bounds.
875 
876  These will be methods like
877  const double * const IndexTable0(double index_var);
878  if self.row_lookup_method, or like
879  void IndexTable0(double index_var, unsigned& index, double& factor);
880  otherwise, with
881  bool CheckIndex0(double& index_var);
882  for checking the bounds.
883  """
884  for key, idx in self.doc.lookup_table_indexes.iteritems():
885  varname = self.code_name(key[-1])
886  method_name = 'IndexTable' + str(idx)
887  if self.row_lookup_method:
888  method = 'const double * %s(double %s)' % (method_name, varname)
889  else:
890  factor = self.lut_factor(idx)
891  idx_var = '_table_index_' + str(idx)
892  if factor:
893  factor = ', double& ' + factor
894  method = 'void %s(double %s, unsigned& %s%s)' % (method_name, varname, idx_var, factor)
895  self.writeln(method)
896  self.open_block()
897  self.output_table_index_generation_code(key, idx, call_method=False)
898  if self.row_lookup_method:
899  self.writeln('return _lt_', idx, '_row;')
900  self.close_block()
901  # And check the indexes
902  if self.config.options.check_lt_bounds:
903  self.writeln('#define COVERAGE_IGNORE', indent=False)
904  self.writeln('bool CheckIndex', idx, '(double& ', varname, ')')
905  self.open_block()
906  self.output_table_index_checking(key, idx, call_method=False)
907  self.writeln('return _oob_', idx, self.STMT_END)
908  self.close_block(blank_line=False)
909  self.writeln('#undef COVERAGE_IGNORE\n', indent=False)
910 
911  def output_table_index_checking(self, key, idx, call_method=True):
912  """Override base class method to call the methods on the lookup table class if needed."""
913  if self.separate_lut_class and call_method:
914  if self.config.options.check_lt_bounds:
915  var = key[-1]
916  varname = self.code_name(var)
917  self.writeln('const bool _oob_', idx, self.EQ_ASSIGN, self.lt_class_name,
918  '::Instance()->CheckIndex', idx, '(', varname, ')', self.STMT_END)
919  else:
920  super(CellMLToNektarTranslator, self).output_table_index_checking(key, idx)
921 
922  def output_table_index_generation_code(self, key, idx, call_method=True):
923  """Override base class method to call the methods on the lookup table class if needed."""
924  if self.separate_lut_class and call_method:
925  var = key[-1]
926  varname = self.code_name(var)
927  method_name = self.lt_class_name + '::Instance()->IndexTable' + str(idx)
928  if self.row_lookup_method:
929  self.writeln('const double* const _lt_', idx, '_row = ', method_name, '(', varname, ');')
930  else:
931  factor = self.lut_factor(idx, include_comma=True)
932  idx_var = '_table_index_' + str(idx)
933  self.writeln(method_name, '(', varname, ', ', idx_var, factor, ');')
934  else:
935  super(CellMLToNektarTranslator, self).output_table_index_generation_code(key, idx)
936 
937  def output_lut_class(self):
938  """Output a separate class for lookup tables.
939 
940  This will live entirely in the .cpp file."""
941  # Lookup tables class
942  self.writeln('class ', self.lt_class_name, ' : public AbstractLookupTableCollection')
943  self.writeln('{')
944  self.writeln('public:')
945  self.set_indent(1)
946  # Method to get the table instance object
947  self.writeln('static ', self.lt_class_name, '* Instance()')
948  self.open_block()
949  self.writeln('if (mpInstance.get() == NULL)')
950  self.writeln('{')
951  self.writeln('mpInstance.reset(new ', self.lt_class_name, ');', indent_offset=1)
952  self.writeln('}')
953  self.writeln('return mpInstance.get();')
954  self.close_block()
955  # Method to free the table memory
956  self.writeln('void FreeMemory()')
957  self.open_block()
958  self.output_lut_deletion()
959  self.writeln('mNeedsRegeneration.assign(mNeedsRegeneration.size(), true);')
960  self.close_block()
961  # Table lookup methods
962  self.output_lut_methods()
964  # Destructor
965  self.writeln('~', self.lt_class_name, '()')
966  self.open_block()
967  self.output_lut_deletion()
968  self.close_block()
969  # Make the class a singleton
970  self.writeln('protected:', indent_level=0)
971  self.writeln(self.lt_class_name, '(const ', self.lt_class_name, '&);')
972  self.writeln(self.lt_class_name, '& operator= (const ', self.lt_class_name, '&);')
973  # Constructor
974  self.writeln(self.lt_class_name, '()')
975  self.open_block()
976  self.writeln('assert(mpInstance.get() == NULL);')
977  if self.config.options.include_dt_in_tables:
978  self.writeln('mDt = HeartConfig::Instance()->GetOdeTimeStep();')
979  self.writeln('assert(mDt > 0.0);')
980  num_indexes = len(self.doc.lookup_table_indexes)
981  self.writeln('mKeyingVariableNames.resize(', num_indexes, ');')
982  self.writeln('mNumberOfTables.resize(', num_indexes, ');')
983  self.writeln('mTableMins.resize(', num_indexes, ');')
984  self.writeln('mTableSteps.resize(', num_indexes, ');')
985  self.writeln('mTableStepInverses.resize(', num_indexes, ');')
986  self.writeln('mTableMaxs.resize(', num_indexes, ');')
987  self.writeln('mNeedsRegeneration.resize(', num_indexes, ');')
988  for key, idx in self.doc.lookup_table_indexes.iteritems():
989  min, max, step, var = key
990  num_tables = unicode(self.doc.lookup_tables_num_per_index[idx])
991  self.writeln('mKeyingVariableNames[', idx, '] = "', self.var_display_name(var), '";')
992  self.writeln('mNumberOfTables[', idx, '] = ', num_tables, self.STMT_END)
993  self.writeln('mTableMins[', idx, '] = ', min, self.STMT_END)
994  self.writeln('mTableSteps[', idx, '] = ', step, self.STMT_END)
995  self.writeln('mTableStepInverses[', idx, '] = ', str(1/float(step)), self.STMT_END)
996  self.writeln('mTableMaxs[', idx, '] = ', max, self.STMT_END)
997  self.writeln('mNeedsRegeneration[', idx, '] = true;')
998  self.writeln('_lookup_table_', idx, self.EQ_ASSIGN, 'NULL', self.STMT_END)
999  self.writeln(self.lt_class_name, '::RegenerateTables();')
1000  self.close_block()
1001  # Table (re-)generation
1002  self.writeln('void RegenerateTables()')
1003  self.open_block()
1004  event_handler = 'AbstractLookupTableCollection::EventHandler::'
1005  self.writeln(event_handler, 'BeginEvent(', event_handler, 'GENERATE_TABLES);')
1006  if self.config.options.include_dt_in_tables:
1007  self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(self.config.dt_variable), ' = mDt;')
1008  # Hack: avoid unused variable warning
1009  self.writeln('double _unused = ', self.code_name(self.config.dt_variable), ';')
1010  self.writeln('_unused = _unused;\n')
1011  for idx in self.doc.lookup_table_indexes.itervalues():
1012  self.writeln('if (mNeedsRegeneration[', idx, '])')
1013  self.open_block()
1014  self.output_lut_deletion(only_index=idx)
1015  self.output_lut_generation(only_index=idx)
1016  self.writeln('mNeedsRegeneration[', idx, '] = false;')
1017  self.close_block(blank_line=True)
1018  self.writeln(event_handler, 'EndEvent(', event_handler, 'GENERATE_TABLES);')
1019  self.close_block()
1020  # Private data
1021  self.writeln('private:', indent_level=0)
1022  self.writeln('/** The single instance of the class */')
1023  self.writeln('static std::auto_ptr<', self.lt_class_name, '> mpInstance;\n')
1024  if self.row_lookup_method:
1027  # Close the class
1028  self.set_indent(0)
1029  self.writeln('};\n')
1030  # Define the instance pointer
1031  self.writeln('std::auto_ptr<', self.lt_class_name, '> ', self.lt_class_name, '::mpInstance;')
1032  self.writeln()
1033  return
1034 
1035  def output_state_assignments(self, exclude_nonlinear=False,
1036  assign_rY=True,
1037  nodeset=None,
1038  pointer=''):
1039  """Output statements extracting state variables from their vector.
1040 
1041  If exclude_nonlinear is set to true, state variables appearing
1042  in the nonlinear system will not be included.
1043 
1044  If nodeset is given, only state variables appearing in nodeset
1045  will be included.
1046 
1047  If pointer is given, then the state variables actually appear in the
1048  variable given by pointer, which is of type const std::vector<double>*.
1049  """
1050  used_vars = set()
1051  for var in self.state_vars:
1052  if ((not exclude_nonlinear or var not in self.nonlinear_system_vars)
1053  and (nodeset is None or var in nodeset)):
1054  used_vars.add(var)
1055  if assign_rY and used_vars:
1056  if pointer:
1057  self.output_comment('For state variable interpolation (SVI) we read in interpolated state variables,')
1058  self.output_comment('otherwise for ionic current interpolation (ICI) we use the state variables of this model (node).')
1059  if self.TYPE_VECTOR_REF == CellMLToNektarTranslator.TYPE_VECTOR_REF:
1060  self.writeln('if (!%s) %s = &rGetStateVariables();' % (pointer, pointer))
1061  self.writeln('const ', self.TYPE_VECTOR_REF, 'rY = *', pointer, self.STMT_END)
1062  else:
1063  self.writeln(self.TYPE_VECTOR_REF, 'rY;')
1064  self.writeln('bool made_new_cvode_vector = false;')
1065  self.writeln('if (!%s)' % (pointer))
1066  self.open_block()
1067  self.writeln('rY = rGetStateVariables();')
1068  self.close_block(False)
1069  self.writeln('else')
1070  self.open_block()
1071  self.writeln('made_new_cvode_vector = true;')
1072  self.writeln('rY = MakeNVector(*%s);' % (pointer))
1073  self.close_block()
1074  else:
1075  self.writeln(self.TYPE_VECTOR_REF, 'rY = rGetStateVariables();')
1076  if self.options.protocol:
1077  low_prop = ('pycml:range-low', NSS['pycml'])
1078  high_prop = ('pycml:range-high', NSS['pycml'])
1079  def check_bound(prop, reln, var, value):
1080  prop_value = var.get_rdf_annotation(prop)
1081  if prop_value:
1082  value = '(%s %s %s ? %s : %s)' % (value, reln, prop_value, prop_value, value)
1083  return value
1084  for i, var in enumerate(self.state_vars):
1085  if var in used_vars:
1086  if self.use_modifiers and var in self.modifier_vars:
1087  value = self.modifier_call(var, self.vector_index('rY', i))
1088  else:
1089  value = self.vector_index('inarray', i) + '[i]'
1090  if self.options.protocol:
1091  value = check_bound(low_prop, '<', var, value)
1092  value = check_bound(high_prop, '>', var, value)
1093  #2116 - use supplied fixed voltage if we're clamping
1094  # if var is self.v_variable:
1095  # value = '(mSetVoltageDerivativeToZero ? this->mFixedVoltage : %s)' % value
1096  self.writeln(self.TYPE_DOUBLE, self.code_name(var),
1097  self.EQ_ASSIGN, value, self.STMT_END)
1098  self.writeln(self.COMMENT_START, 'Units: ', var.units,
1099  '; Initial value: ',
1100  getattr(var, u'initial_value', 'Unknown'))
1101  #621 TODO: maybe convert if state var dimensions include time
1102  self.writeln()
1103  return
1104 
1105  def modifier_call(self, var, current_value):
1106  """Return code for a call to a modifier function for an oxmeta-annotated variable.
1107 
1108  The modifier function takes 2 parameters: the current value of the variable,
1109  and the current time. It returns a modified value for the variable.
1110  """
1111  return ('mp_' + var.oxmeta_name + '_modifier->Calc(' +
1112  current_value + ', ' + self.code_name(self.free_vars[0]) + ')')
1113 
1114  def vector_index(self, vector, i):
1115  """Return code for accessing the i'th index of vector."""
1116  return vector + '[' + str(i) + ']'
1117 
1118  def vector_create(self, vector, size):
1119  """Return code for creating a new vector with the given size."""
1120  return ''.join(map(str, [self.TYPE_VECTOR, vector, '(', size, ')', self.STMT_END]))
1121 
1122  def vector_initialise(self, vector, size):
1123  """Return code for creating an already-declared vector with the given size."""
1124  return ''.join(map(str, [vector, '.resize(', size, ')', self.STMT_END]))
1125 
1126  def output_nonlinear_state_assignments(self, nodeset=None):
1127  """Output assignments for nonlinear state variables."""
1128  for i, var in enumerate(self.nonlinear_system_vars):
1129  if not nodeset or var in nodeset:
1130  self.writeln(self.TYPE_DOUBLE, self.code_name(var), self.EQ_ASSIGN,
1131  self.vector_index('rCurrentGuess', i), self.STMT_END)
1132  #621 TODO: maybe convert if state var dimensions include time
1133  self.writeln()
1134  return
1135 
1137  """Return code for getting Chaste's stimulus current."""
1138  expr = self.doc._cml_config.i_stim_var
1139  output = self.code_name(expr) + self.EQ_ASSIGN
1140  get_stim = 'GetIntracellularAreaStimulus(' + self.code_name(self.free_vars[0]) + ')'
1141  if self.doc._cml_config.i_stim_negated:
1142  get_stim = '-' + get_stim
1143  return output + get_stim + self.STMT_END
1144 
1145  def output_equations(self, nodeset, zero_stimulus=False):
1146  """Output the mathematics described by nodeset.
1147 
1148  nodeset represents a subset of the assignments in the model.
1149  Output assignments in the order given by a topological sort,
1150  but only include those in nodeset.
1151  """
1152  # Special case for the stimulus current
1153 
1154 
1155  if self.doc._cml_config.i_stim_var in nodeset:
1156  if zero_stimulus:
1157  i_stim = self.doc._cml_config.i_stim_var
1158  stim_assignment = self.code_name(i_stim) + self.EQ_ASSIGN + '0.0' + self.STMT_END
1159  else:
1160  stim_assignment = self.get_stimulus_assignment()
1161 
1162  for expr in (e for e in self.model.get_assignments() if e in nodeset):
1163 
1164  # Special-case the stimulus current
1165  # self.writeln(expr)
1166  # if self.use_chaste_stimulus or zero_stimulus:
1167  # if isinstance(expr, cellml_variable) and expr is self.doc._cml_config.i_stim_var:
1168  # self.writeln(self.TYPE_CONST_DOUBLE, stim_assignment)
1169  # elif not (isinstance(expr, mathml_apply) and
1170  # isinstance(expr.operator(), mathml_eq) and
1171  # isinstance(expr.eq.lhs, mathml_ci) and
1172  # expr.eq.lhs.variable is self.doc._cml_config.i_stim_var):
1173  # self.output_assignment(expr)
1174 
1175 
1176  self.output_assignment(expr)
1177  return
1178 
1179  def output_assignment(self, expr):
1180  """Output an assignment statement.
1181 
1182  Has overrides for various special cases.
1183  """
1184  clear_type = False
1185  writing_data_clamp_current = False
1186  # Figure out what is being assigned to
1187  if isinstance(expr, cellml_variable):
1188  assigned_var = expr
1189  else:
1190  if expr.eq.lhs.localName == 'ci':
1191  assigned_var = expr.eq.lhs.variable
1192  if assigned_var is self.config.i_data_clamp_current:
1193  writing_data_clamp_current = True
1194  self.output_comment('Special handling of data clamp current here (see #2708)')
1195  self.output_comment('(we want to save expense of calling the interpolation method if possible.)')
1196  self.writeln(self.TYPE_DOUBLE, self.code_name(assigned_var), self.EQ_ASSIGN, '0.0' , self.STMT_END)
1197  self.writeln('if (mDataClampIsOn)')
1198  self.open_block()
1199  clear_type = True
1200  else:
1201  assigned_var = None # We don't store derivatives as members
1202  #907: Check if this is the derivative of the transmembrane potential
1203  if not self.use_backward_euler and expr.eq.lhs.diff.dependent_variable == self.v_variable:
1204  clear_type = True
1205 
1206  # Parameters don't need assigning
1207  has_modifier = self.use_modifiers and getattr(assigned_var, '_cml_has_modifier', False)
1208  # self.writeln(has_modifier)
1209  if assigned_var in self.cell_parameters and not has_modifier:
1210  return
1211  # Is the variable declared elsewhere?
1212  if clear_type:
1214  elif getattr(assigned_var, '_cml_modifiable', False):
1215  # Override const-ness, e.g. for a lookup table index
1216  self.TYPE_CONST_DOUBLE = self.TYPE_DOUBLE
1217  if (assigned_var and self.use_modifiers and assigned_var in self.modifier_vars
1218  and assigned_var.get_type() != VarTypes.State):
1219  # "Constant" oxmeta-annotated parameters may be modified at run-time
1220  if has_modifier:
1221  # Turn off the modifier to figure out the base value
1222  assigned_var._cml_has_modifier = False
1223  rhs = self.code_name(assigned_var)
1224  assigned_var._cml_has_modifier = True
1225  else:
1226  self.capture_output()
1227  super(CellMLToNektarTranslator, self).output_assignment(expr)
1228  assignment = self.get_captured_output()
1229  eq_pos = assignment.find(self.EQ_ASSIGN)
1230  end_pos = assignment.find(self.STMT_END)
1231  rhs = assignment[eq_pos+len(self.EQ_ASSIGN):end_pos]
1232  if rhs:
1233  # If assigned_var is computed, it'll 'appear' twice - once with expr==assigned_var,
1234  # and once for the assignment mathml_apply. The former will result in an empty rhs.
1235  # self.writeln('Test')
1236  self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(assigned_var), self.EQ_ASSIGN,
1237  self.modifier_call(assigned_var, rhs), self.STMT_END, nl=False)
1238 
1239  self.output_comment(assigned_var.units, indent=False, pad=True)
1240  else:
1241  # self.writeln('Test')
1242  super(CellMLToNektarTranslator, self).output_assignment(expr)
1243 # if assigned_var:
1244 # # Debug
1245 # self.writeln('EXCEPT_IF_NOT(!std::isinf(', self.code_name(assigned_var), '));')
1246 # self.writeln('EXCEPT_IF_NOT(!std::isnan(', self.code_name(assigned_var), '));')
1247  if clear_type:
1248  # Remove the instance attributes, thus reverting to the class members
1249  del self.TYPE_DOUBLE
1250  del self.TYPE_CONST_DOUBLE
1251  elif getattr(assigned_var, '_cml_modifiable', False):
1252  del self.TYPE_CONST_DOUBLE
1253 
1254  if writing_data_clamp_current:
1255  self.close_block(False)
1256 
1257  return
1258 
1260  """Output the mathematics in this model.
1261 
1262  When backward Euler is used, we do so in 5 methods:
1263  * UpdateTransmembranePotential does a forward Euler step for V
1264  * ComputeOneStepExceptVoltage co-ordinates a backward Euler step
1265  * ComputeResidual and ComputeJacobian are used in the Newton iteration
1266  * GetIIonic returns the total ionic current
1267 
1268  Rush-Larsen is implemented similarly, with:
1269  * EvaluateEquations evaluate the model derivatives and alpha/beta terms
1270  * ComputeOneStepExceptVoltage does a Rush-Larsen update for eligible variables,
1271  and a forward Euler step for other non-V state variables
1272  Generalised Rush-Larsen methods also have specialised handling; see the
1273  individual methods for details.
1274 
1275  For other solvers, only 2 methods are needed:
1276  * EvaluateYDerivatives computes the RHS of the ODE system
1277  * GetIIonic is as above
1278 
1279  Where derived-quantity annotations are present, we also generate a
1280  ComputeDerivedQuantities method.
1281  """
1282  #self.output_get_i_ionic()
1283  if self.options.rush_larsen:
1285  elif self.use_backward_euler:
1287  elif self.options.grl1:
1289  elif self.options.grl2:
1291  else:
1294 
1295  def calculate_lookup_table_indices(self, nodeset, time_name=None):
1296  """Output the lookup table index calculations needed for the given equations, if tables are enabled.
1297 
1298  If time_name is given, it may be used in exception messages for tables out of bounds.
1299  Note that it is needed to be passed in, since GetIIonic does not know the time.
1300 
1301  Returns the subset of nodeset used in calculating the indices.
1302  """
1303  if self.use_lookup_tables:
1304  nodes_used = self.output_table_index_generation(time_name, nodeset=nodeset)
1305  else:
1306  nodes_used = set()
1307  return nodes_used
1308 
1310  """Output the GetIIonic method."""
1311  use_modifiers = self.use_modifiers
1312  self.use_modifiers = False
1313  self.output_method_start('GetIIonic', ['const std::vector<double>* pStateVariables'],
1314  self.TYPE_DOUBLE, access='public', defaults=['NULL'])
1315  self.open_block()
1316  # Output mathematics to calculate ionic current, using solver_info.ionic_current.
1317  if (hasattr(self.model, u'solver_info') and hasattr(self.model.solver_info, u'ionic_current')):
1318  if not hasattr(self.model.solver_info.ionic_current, u'var'):
1319  raise ValueError('No ionic currents found; check your configuration file')
1320  nodes = map(lambda elt: self.varobj(unicode(elt)),
1321  self.model.solver_info.ionic_current.var)
1322  # GetIIonic must not include the stimulus current
1323  i_stim = self.doc._cml_config.i_stim_var
1324  nodeset = self.calculate_extended_dependencies(nodes, prune_deps=[i_stim])
1325  #print map(lambda v: v.fullname(), nodes)
1326  #print filter(lambda p: p[2]>0, map(debugexpr, nodeset))
1327  # Output main part of maths
1328  self.output_state_assignments(nodeset=nodeset, pointer='pStateVariables')
1329  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset)
1330  self.output_equations(nodeset - table_index_nodes_used, zero_stimulus=True)
1331  self.writeln()
1332  # Assign the total current to a temporary so we can check for NaN
1333  self.writeln(self.TYPE_CONST_DOUBLE, 'i_ionic', self.EQ_ASSIGN, nl=False)
1334  if self.doc._cml_config.i_ionic_negated:
1335  self.writeln('-(', nl=False, indent=False)
1336  plus = False
1337  for varelt in self.model.solver_info.ionic_current.var:
1338  if plus: self.write('+')
1339  else: plus = True
1340  self.output_variable(varelt)
1341  if self.doc._cml_config.i_ionic_negated:
1342  self.writeln(')', nl=False, indent=False)
1343  self.writeln(self.STMT_END, indent=False)
1344  """if self.TYPE_VECTOR_REF == CellMLToCvodeTranslator.TYPE_VECTOR_REF:
1345  self.writeln('if (made_new_cvode_vector)')
1346  self.open_block()
1347  self.writeln('DeleteVector(rY);')
1348  self.close_block(False)"""
1349  self.writeln('EXCEPT_IF_NOT(!std::isnan(i_ionic));')
1350  self.writeln('return i_ionic', self.STMT_END)
1351  else:
1352  self.writeln('return 0.0;')
1353  self.close_block()
1354  self.use_modifiers = use_modifiers
1355 
1356  def output_evaluate_y_derivatives(self, method_name='EvaluateYDerivatives'):
1357  """Output the EvaluateYDerivatives method."""
1358  # Start code output
1359 
1360  #write the method start
1361  self.writeln(self.TYPE_VOID, self.class_name, '::v_Update(')
1362  self.writeln('const Array<OneD, const Array<OneD, NekDouble> >&inarray,', indent_offset=4)
1363  self.writeln(' Array<OneD, Array<OneD, NekDouble> >&outarray,', indent_offset=5)
1364  self.writeln(' const NekDouble var_chaste_interface__environment__time)', indent_offset=13)
1365  self.open_block()
1366  self.writeln('for (unsigned int i = 0; i < m_nq; ++i)')
1367 
1368  self.open_block()
1369  if not self.state_vars:
1370  # This isn't an ODE model!
1371  self.close_block()
1372  return
1373  self.output_comment('Inputs:')
1374  self.output_comment('Time units: ', self.free_vars[0].units)
1376 
1377  # debugging help by writing out the arrays of taus, infs, alphas, betas and dictionary of variable types
1378  # self.writeln('Taus: ' + str(self.taus))
1379  # self.writeln('Infs: ' + str(self.infs))
1380  # self.writeln('Alphas: ' + str(self.alphas))
1381  # self.writeln('Betas: ' + str(self.betas))
1382  # self.writeln('Var Types: ' + str(self.state_var_type)+ '\n')
1383 
1384  #write the taus
1385  self.output_comment('Calculating the tau-values:')
1386  for i,var in enumerate(self.state_vars):
1387  var_name = self.code_name(var, True)
1388  var_actual = str(var)[33:str(var).rfind('__')]
1389  if 'gate' in str(var_name):
1390  after_underscore = var_name[var_name.rfind('__')+2:]
1391 
1392 
1393  if 'membrane__V' in var:
1394  continue
1395 
1396  if self.state_var_type[str(var)] == 'm_gates':
1397  if filter(lambda element: var_actual in element,self.taus):
1398  self.output_comment('The tau value for ' + str(var_actual) + ' should already be calculated in the mathematics')
1399  else:
1400  if filter(lambda element: var_actual in element,self.alphas) and filter(lambda element: var_actual in element,self.betas):
1401  alpha = filter(lambda element: var_actual in element,self.alphas)[0]
1402  beta = filter(lambda element: var_actual in element,self.betas)[0]
1403  new_tau = 'var_' + str(var_actual) + '__tau_' + str(after_underscore)
1404  self.writeln('const NekDouble ' + str(new_tau) + ' = 1.0 / (' + str(alpha) + ' + ' + str(beta) + ');')
1405  self.taus.append(new_tau)
1406  elif self.state_var_type[str(var)] == 'm_concentrations':
1407  continue
1408  self.writeln()
1409 
1410  #write the infs
1411  self.output_comment('Calculating the inf-values:')
1412  for i,var in enumerate(self.state_vars):
1413  var_name = self.code_name(var, True)
1414  var_actual = str(var)[33:str(var).rfind('__')]
1415  if 'gate' in str(var_name):
1416  after_underscore = var_name[var_name.rfind('__')+2:]
1417 
1418 
1419  if 'membrane__V' in str(var):
1420  continue
1421 
1422  if self.state_var_type[str(var)] == 'm_gates':
1423  if filter(lambda element: var_actual in element,self.infs):
1424  self.output_comment('The inf value for ' + str(var_actual) + ' should already be calculated in the mathematics')
1425  else:
1426  if filter(lambda element: var_actual in element,self.alphas) and filter(lambda element: var_actual in element,self.betas):
1427  alpha = filter(lambda element: var_actual in element,self.alphas)[0]
1428  beta = filter(lambda element: var_actual in element,self.betas)[0]
1429  new_inf = 'var_' + str(var_actual) + '__' + str(after_underscore) + '_inf'
1430  self.writeln('const NekDouble ' + str(new_inf) + ' = ' + str(alpha) + ' / (' + str(alpha) + ' + ' + str(beta) + ');')
1431  self.infs.append(new_inf)
1432  elif self.state_var_type[str(var)] == 'm_concentrations':
1433  continue
1434  self.writeln()
1435 
1436  #debugging help by writing out the taus, infs and dictionary of variable types
1437  # self.writeln('Taus: ' + str(self.taus))
1438  # self.writeln('Infs: ' + str(self.infs))
1439  # self.writeln('Var Types: ' + str(self.state_var_type)+ '\n')
1440 
1441 
1442  #writing the outarrays
1443  self.output_comment('Writing the outarrays:')
1444  m_gate_tau_counter = 0
1445 
1446  for i,var in enumerate(self.state_vars):
1447  var_name = self.code_name(var, True)
1448  var_actual = str(var)[33:str(var).rfind('__')]
1449  if 'gate' in str(var_name):
1450  after_underscore = var_name[var_name.rfind('__')+2:]
1451 
1452 
1453 
1454  if 'membrane__V' in str(var):
1455  self.writeln('outarray[',i,'][i] = ' + str(var_name) + ';')
1456 
1457  if self.state_var_type[str(var)] == 'm_gates':
1458  inf_value = filter(lambda element: var_actual in element,self.infs)[0]
1459  tau_value = filter(lambda element: var_actual in element,self.taus)[0]
1460  self.writeln('outarray[',i,'][i] = ' + inf_value + ';')
1461  self.writeln('m_gates_tau[',m_gate_tau_counter,'][i] = ' + tau_value + ';')
1462  m_gate_tau_counter += 1
1463 
1464  if self.state_var_type[str(var)] == 'm_concentrations':
1465  self.writeln('outarray[',i,'][i] = ' + str(var_name) + ';')
1466 
1467  # self.writeln()
1468 
1469 
1470 
1471  self.close_block()
1472  self.close_block()
1473 
1474 
1475  def output_derivative_calculations(self, state_vars, assign_rY=False, extra_nodes=set(),
1476  extra_table_nodes=set()):
1477  """
1478  This is used by self.output_evaluate_y_derivatives and self.output_rush_larsen_mathematics
1479  to compute the derivatives (and any extra nodes, if given). It contains the special logic
1480  to obey the mSetVoltageDerivativeToZero member variable in the generated code.
1481  Returns a nodeset containing the equations output.
1482  """
1483  # Work out what equations are needed to compute the derivatives
1484 
1485  #this is important and might be where the variables come from
1486  #derivs creates a set of lists of tuples, where each tuple is a state_var with the free_var
1487  derivs = set(map(lambda v: (v, self.free_vars[0]), state_vars))
1488  # for var in derivs:
1489  # self.writeln(var)
1490 
1491 
1492  # self.writeln(derivs) #this line shows that set
1493 
1494  if self.v_variable in state_vars: #what this does is take out the v_variable into its own tuple and calls it dvdt
1495 
1496  dvdt = (self.v_variable, self.free_vars[0])
1497  derivs.remove(dvdt) #907: Consider dV/dt separately
1498  else:
1499  dvdt = None
1500 
1501  #this if function has been outcommented because i think it might not actually be needed in nektar
1502  # if self.use_chaste_stimulus:
1503  # i_stim = [self.doc._cml_config.i_stim_var]
1504  # else:
1505  i_stim = []
1506 
1507 
1508  nonv_nodeset = self.calculate_extended_dependencies(derivs|extra_nodes, prune_deps=i_stim)
1509 
1510  if dvdt:
1511  if self.use_data_clamp:
1512  prune = set([self.config.i_data_clamp_data]) | nonv_nodeset
1513  else:
1514  prune = nonv_nodeset
1515  # self.writeln('uses dvdt')
1516  v_nodeset = self.calculate_extended_dependencies([dvdt], prune=prune, prune_deps=i_stim)
1517  else:
1518  v_nodeset = set()
1519  # State variable inputs
1520  all_nodes = nonv_nodeset|v_nodeset
1521  self.output_state_assignments(assign_rY=assign_rY, nodeset=all_nodes)
1522  self.writeln()
1523  table_index_nodes_used = self.calculate_lookup_table_indices(all_nodes|extra_table_nodes, self.code_name(self.free_vars[0]))
1524  self.output_comment('Mathematics')
1525  #907: Declare dV/dt
1526  if dvdt:
1527  self.writeln(self.TYPE_DOUBLE, self.code_name(self.v_variable, ode=True), self.STMT_END)
1528  # Output mathematics required for non-dV/dt derivatives (which may include dV/dt)
1529  # self.writeln('Test Begin')
1530  self.NODESET = nonv_nodeset - table_index_nodes_used
1531  self.output_equations(nonv_nodeset - table_index_nodes_used)
1532  # self.writeln('Test End')
1533 
1534 
1535 
1536  self.writeln()
1537 
1538  #907: Calculation of dV/dt
1539  # if dvdt:
1540  # self.writeln('if (mSetVoltageDerivativeToZero)')
1541  # self.open_block()
1542  # self.writeln(self.code_name(self.v_variable, ode=True), self.EQ_ASSIGN, '0.0', self.STMT_END)
1543  # self.close_block(blank_line=False)
1544  # self.writeln('else')
1545  # self.open_block()
1546 
1547  # self.writeln('Test')
1548  # self.writeln(v_nodeset)
1549 
1550  # to_remove = set()
1551  # for var in v_nodeset:
1552  # # self.writeln(var)
1553  # if ',stim_' in str(var) or 'membrane,time' in str(var) and not 'membrane,i_st' in str(var):
1554  # to_remove.add(var)
1555 
1556  # # self.writeln(to_remove)
1557 
1558  # for var in to_remove:
1559  # v_nodeset.remove(var)
1560 
1561  self.writeln(self.TYPE_CONST_DOUBLE,'var_chaste_interface__membrane__I_stim = 0.0;')
1562  # self.writeln(self.TYPE_CONST_DOUBLE,'var_membrane__I_stim = var_chaste_interface__membrane__I_stim; // microA_per_cm2')
1563  self.output_equations(v_nodeset - table_index_nodes_used)
1564  # self.writeln('Test End')
1565 
1566  # self.close_block()
1567 
1568  self.writeln()
1569 
1570  return all_nodes | table_index_nodes_used
1571 
1573  """Output the mathematics methods used in a backward Euler cell.
1574 
1575  Outputs ComputeResidual, ComputeJacobian,
1576  UpdateTransmembranePotential and ComputeOneStepExceptVoltage.
1577  """
1578  dt_name = 'mDt'
1579  #model_dt = self.varobj(self.model.solver_info.dt)
1580  if self.nonlinear_system_size > 0:
1581  # Residual
1582  ##########
1583  argsize = '[' + str(self.nonlinear_system_size) + ']'
1584  self.output_method_start('ComputeResidual',
1585  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
1586  self.TYPE_CONST_DOUBLE + 'rCurrentGuess' + argsize,
1587  self.TYPE_DOUBLE + 'rResidual' + argsize],
1588  'void', access='public')
1589  self.open_block()
1590  # Output mathematics for computing du/dt for each nonlinear state var u
1591  nodes = map(lambda u: (u, self.free_vars[0]), self.nonlinear_system_vars)
1592  nodeset = self.calculate_extended_dependencies(nodes, prune_deps=[self.doc._cml_config.i_stim_var])
1593  self.output_state_assignments(exclude_nonlinear=True, nodeset=nodeset)
1594  self.output_nonlinear_state_assignments(nodeset=nodeset)
1595  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset, self.code_name(self.free_vars[0]))
1596  self.output_equations(nodeset - table_index_nodes_used)
1597  self.writeln()
1598  # Fill in residual
1599  for i, var in enumerate(self.state_vars):
1600  try:
1601  j = self.nonlinear_system_vars.index(var)
1602  except ValueError:
1603  j = -1
1604  if j != -1:
1605  self.writeln('rResidual[', j, '] = rCurrentGuess[', j, '] - rY[', i, '] - ',
1606  dt_name, '*', self.code_name(var, ode=True), self.STMT_END)
1607  self.close_block()
1608 
1609  # Jacobian
1610  ##########
1611  self.output_method_start('ComputeJacobian',
1612  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
1613  self.TYPE_CONST_DOUBLE + 'rCurrentGuess' + argsize,
1614  self.TYPE_DOUBLE + 'rJacobian' + argsize + argsize],
1615  'void', access='public')
1616  self.open_block()
1617  # Mathematics that the Jacobian depends on
1618  used_vars = set()
1619  for entry in self.model.solver_info.jacobian.entry:
1620  used_vars.update(self._vars_in(entry.math))
1621  nodeset = self.calculate_extended_dependencies(used_vars, prune_deps=[self.doc._cml_config.i_stim_var])
1622  self.output_state_assignments(exclude_nonlinear=True, nodeset=nodeset)
1623  self.output_nonlinear_state_assignments(nodeset=nodeset)
1624  self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(self.config.dt_variable), self.EQ_ASSIGN, dt_name, self.STMT_END, '\n')
1625  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset|set(map(lambda e: e.math, self.model.solver_info.jacobian.entry)), self.code_name(self.free_vars[0]))
1626  self.output_equations(nodeset - table_index_nodes_used)
1627  self.writeln()
1628  # Jacobian entries
1629  for entry in self.model.solver_info.jacobian.entry:
1630  var_i, var_j = entry.var_i, entry.var_j
1631  i = self.nonlinear_system_vars.index(self.varobj(var_i))
1632  j = self.nonlinear_system_vars.index(self.varobj(var_j))
1633  self.writeln('rJacobian[', i, '][', j, '] = ', nl=False)
1634  entry_content = list(entry.math.xml_element_children())
1635  assert len(entry_content) == 1, "Malformed Jacobian matrix entry: " + entry.xml()
1636  self.output_expr(entry_content[0], False)
1637  self.writeln(self.STMT_END, indent=False)
1638 # self.output_comment('Debugging')
1639 # self.writeln('#ifndef NDEBUG', indent=False)
1640 # self.writeln('for (unsigned i=0; i<', len(self.nonlinear_system_vars), '; i++)')
1641 # self.writeln('for (unsigned j=0; j<', len(self.nonlinear_system_vars), '; j++)', indent_offset=1)
1642 # self.writeln('EXCEPT_IF_NOT(!std::isnan(rJacobian[i][j]));', indent_offset=2)
1643 # self.writeln('//DumpJacobianToFile(', self.code_name(self.free_vars[0]),
1644 # ', rCurrentGuess, rJacobian, rY);')
1645 # self.writeln('#endif // NDEBUG', indent=False)
1646  self.close_block()
1647  # The other methods are protected
1648  self.writeln_hpp('protected:', indent_offset=-1)
1649 
1650  # UpdateTransmembranePotential
1651  ##############################
1652  self.output_method_start('UpdateTransmembranePotential',
1653  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0])],
1654  'void', access='public')
1655  self.open_block()
1656  self.output_comment('Time units: ', self.free_vars[0].units)
1657  # Output mathematics to compute dV/dt
1658  nodes = [(self.state_vars[self.v_index], self.free_vars[0])]
1659  nodeset = self.calculate_extended_dependencies(nodes, prune_deps=[self.doc._cml_config.i_stim_var])
1660  self.output_state_assignments(nodeset=nodeset)
1661  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset, self.code_name(self.free_vars[0]))
1662  self.output_equations(nodeset - table_index_nodes_used)
1663  # Update V
1664  self.writeln()
1665  self.writeln('rY[', self.v_index, '] += ', dt_name, '*',
1666  self.code_name(self.state_vars[self.v_index], ode=True), self.STMT_END)
1667  self.close_block()
1668 
1669  # ComputeOneStepExceptVoltage
1670  #############################
1671  self.output_method_start('ComputeOneStepExceptVoltage',
1672  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0])],
1673  'void', access='public')
1674  self.open_block()
1675  self.writeln(self.COMMENT_START, 'Time units: ',
1676  self.free_vars[0].units)
1677  # Output mathematics to update linear state variables, using solver_info.linear_odes.
1678  # Also need to use output_equations for variables used in the update equations.
1679  linear_vars, update_eqns = [], {}
1680  used_vars = set() # NB: Also contains update equation if is a mathml_apply so table index generation works
1681  for u, t, update_eqn in SolverInfo(self.model).get_linearised_odes():
1682  assert t == self.free_vars[0]
1683  assert len(update_eqn) == 1
1684  update_eqn = update_eqn[0]
1685  linear_vars.append(u)
1686  update_eqns[id(u)] = update_eqn
1687  if not isinstance(update_eqn, mathml_cn): used_vars.add(update_eqn)
1688  used_vars.update(self._vars_in(update_eqn))
1689  # Output required equations for used variables
1690  nodeset = self.calculate_extended_dependencies(used_vars, prune_deps=[self.doc._cml_config.i_stim_var])
1691  self.output_state_assignments(nodeset=nodeset)
1692  if self.config.dt_variable in nodeset:
1693  self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(self.config.dt_variable), self.EQ_ASSIGN,
1694  dt_name, self.STMT_END, '\n')
1695  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset, self.code_name(self.free_vars[0]))
1696  self.output_equations(nodeset - table_index_nodes_used)
1697  # Update state variables:
1698  # rY[i] = (rY[i] + _g_j*dt) / (1 - _h_j*dt)
1699  self.writeln()
1700  linear_vars.sort(key=lambda v: v.fullname())
1701  for i, u in enumerate(linear_vars):
1702  j = self.state_vars.index(u)
1703  self.writeln('rY[', j, ']', self.EQ_ASSIGN, nl=False)
1704  self.output_expr(update_eqns[id(u)], False)
1705  self.writeln(self.STMT_END, indent=False)
1706  # Set up the Newton iteration, if needed
1707  self.writeln()
1708  if self.nonlinear_system_size > 0:
1709  self.writeln('double _guess[', self.nonlinear_system_size, '] = {', nl=False)
1710  comma = False
1711  idx_map = [0] * self.nonlinear_system_size
1712  for i, var in enumerate(self.state_vars):
1713  try:
1714  j = self.nonlinear_system_vars.index(var)
1715  idx_map[j] = i
1716  except ValueError:
1717  pass
1718  for i in idx_map:
1719  if comma: self.write(',')
1720  else: comma = True
1721  self.write('rY[', i, ']')
1722  self.writeln('};', indent=False)
1723  # Solve
1724  CNS = 'CardiacNewtonSolver<%d,%s>' % (self.nonlinear_system_size, self.class_name)
1725  self.writeln(CNS, '* _p_solver = ', CNS, '::Instance();')
1726  self.writeln('_p_solver->Solve(*this, ', self.code_name(self.free_vars[0]), ', _guess);')
1727  # Update state
1728  for j, i in enumerate(idx_map):
1729  self.writeln('rY[', i, '] = _guess[', j, '];')
1730  self.close_block()
1731 
1733  """Output the special methods needed for Rush-Larsen style cell models.
1734 
1735  We generate:
1736  * EvaluateEquations evaluate the model derivatives and alpha/beta terms
1737  * ComputeOneStepExceptVoltage does a Rush-Larsen update for eligible variables,
1738  and a forward Euler step for other non-V state variables
1739  """
1740  rl_vars = self.doc._cml_rush_larsen
1741  # EvaluateEquations
1742  ###################
1743  self.output_method_start('EvaluateEquations',
1744  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
1745  'std::vector<double> &rDY',
1746  'std::vector<double> &rAlphaOrTau',
1747  'std::vector<double> &rBetaOrInf'],
1748  'void', access='public')
1749  self.open_block()
1750  normal_vars = [v for v in self.state_vars if not v in rl_vars]
1751  nodes, table_nodes = set(), set()
1752  for _, alpha_or_tau, beta_or_inf, _ in rl_vars.itervalues():
1753  table_nodes.add(alpha_or_tau)
1754  nodes.update(self._vars_in(alpha_or_tau))
1755  table_nodes.add(beta_or_inf)
1756  nodes.update(self._vars_in(beta_or_inf))
1757  self.output_derivative_calculations(normal_vars, True, nodes, table_nodes)
1758  # Now assign input vectors
1759  for i, var in enumerate(self.state_vars):
1760  if var in rl_vars:
1761  # Fill in rAlphaOrTau & rBetaOrInf
1762  self.writeln(self.vector_index('rAlphaOrTau', i), self.EQ_ASSIGN, nl=False)
1763  self.output_expr(rl_vars[var][1], False)
1764  self.writeln(self.STMT_END, indent=False)
1765  self.writeln(self.vector_index('rBetaOrInf', i), self.EQ_ASSIGN, nl=False)
1766  self.output_expr(rl_vars[var][2], False)
1767  self.writeln(self.STMT_END, indent=False)
1768  else:
1769  # Fill in rDY
1770  self.writeln(self.vector_index('rDY', i), self.EQ_ASSIGN, self.code_name(var, True), self.STMT_END)
1771  self.close_block()
1772 
1773  # ComputeOneStepExceptVoltage
1774  #############################
1775  self.output_method_start('ComputeOneStepExceptVoltage',
1776  ['const std::vector<double> &rDY',
1777  'const std::vector<double> &rAlphaOrTau',
1778  'const std::vector<double> &rBetaOrInf'],
1779  'void', access='public')
1780  self.open_block()
1781  self.writeln('std::vector<double>& rY = rGetStateVariables();')
1782  for i, var in enumerate(self.state_vars):
1783  if var in rl_vars:
1784  # Rush-Larsen update
1785  conv = rl_vars[var][3] or ''
1786  if conv: conv = '*' + str(conv)
1787  if rl_vars[var][0] == 'ab':
1788  # Alpha & beta formulation
1789  self.open_block()
1790  self.writeln(self.TYPE_CONST_DOUBLE, 'tau_inv = rAlphaOrTau[', i, '] + rBetaOrInf[', i, '];')
1791  self.writeln(self.TYPE_CONST_DOUBLE, 'y_inf = rAlphaOrTau[', i, '] / tau_inv;')
1792  self.writeln('rY[', i, '] = y_inf + (rY[', i, '] - y_inf)*exp(-mDt', conv, '*tau_inv);')
1793  self.close_block(blank_line=False)
1794  else:
1795  # Tau & inf formulation
1796  self.writeln('rY[', i, '] = rBetaOrInf[', i, '] + (rY[', i, '] - rBetaOrInf[', i, '])',
1797  '*exp(-mDt', conv, '/rAlphaOrTau[', i, ']);')
1798  elif var is not self.v_variable:
1799  # Forward Euler update
1800  self.writeln('rY[', i, '] += mDt * rDY[', i, '];')
1801  self.close_block()
1802 
1803  #Megan E. Marsh, Raymond J. Spiteri
1804  #Numerical Simulation Laboratory
1805  #University of Saskatchewan
1806  #December 2011
1807  #Partial support provided by research grants from
1808  #the National Science and Engineering Research
1809  #Council (NSERC) of Canada and the MITACS/Mprime
1810  #Canadian Network of Centres of Excellence.
1811  def output_derivative_calculations_grl(self, var, assign_rY=False, extra_nodes=set(), extra_table_nodes=set()):
1812  """This is used by self.output_grl?_mathematics to get equations for each variable separately.
1813 
1814  Returns a node set with the equations output.
1815  """
1816  # Work out what equations are needed to compute the derivative of var
1817  if var in self.state_vars:
1818  dvardt = (var, self.free_vars[0])
1819  var_nodeset = self.calculate_extended_dependencies([dvardt])
1820  else:
1821  var_nodeset = set()
1822  # State variable inputs
1823  self.output_state_assignments(nodeset=var_nodeset, assign_rY=assign_rY)
1824  self.writeln()
1825  table_index_nodes_used = self.calculate_lookup_table_indices(var_nodeset, self.code_name(self.free_vars[0]))
1826  self.output_comment('Mathematics')
1827  self.output_equations(var_nodeset - table_index_nodes_used)
1828  return var_nodeset | table_index_nodes_used
1829 
1831  """If we have analytic Jacobian information available from Maple, find the terms needed for GRL methods.
1832 
1833  This caches where the diagonal entries are in the matrix, indexed by the state variable objects currently in use,
1834  since the entries in the matrix may reference non-partially-evaluated variables.
1835  """
1836  if not hasattr(self, 'jacobian_diagonal'):
1838  if self.use_analytic_jacobian and not self.jacobian_diagonal:
1839  for entry in self.model.solver_info.jacobian.entry:
1840  if entry.var_i == entry.var_j:
1841  # It's a diagonal entry
1842  var = self.varobj(entry.var_i).get_source_variable(recurse=True)
1843  assert var in self.state_vars, "Jacobian diagonal entry is not in the state vector: " + entry.xml()
1844  entry_content = list(entry.math.xml_element_children())
1845  assert len(entry_content) == 1, "Malformed Jacobian entry: " + entry.xml()
1846  self.jacobian_diagonal[var] = entry_content[0]
1847 
1848  def output_grl_compute_partial(self, i, var):
1849  """Compute the partial derivative of f(var) wrt var, the i'th variable in the state vector.
1850 
1851  This uses an analytic Jacobian if available; otherwise it approximates using finite differences.
1852  """
1853  self.output_method_start('EvaluatePartialDerivative'+str(i),
1854  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
1855  'std::vector<double>& rY', 'double delta', 'bool forceNumerical'],
1856  'double', access='public', defaults=['', '', '', 'false'])
1857  self.open_block()
1858  self.writeln('double partialF;')
1859  if self.jacobian_diagonal:
1860  # Work out what equations are needed to compute the analytic derivative
1861  self.writeln('if (!forceNumerical && this->mUseAnalyticJacobian)')
1862  self.open_block()
1863  entry = self.jacobian_diagonal[var]
1864  nodeset = self.calculate_extended_dependencies(self._vars_in(entry))
1865  self.output_state_assignments(nodeset=nodeset, assign_rY=False)
1866  table_index_nodes_used = self.calculate_lookup_table_indices(nodeset|set([entry]), self.code_name(self.free_vars[0]))
1867  self.output_equations(nodeset)
1868  # Calculate the derivative
1869  self.writeln('partialF = ', nl=False)
1870  self.output_expr(entry, paren=False)
1871  self.writeln(self.STMT_END, indent=False)
1872  self.close_block(blank_line=False)
1873  self.writeln('else')
1874  self.open_block()
1875  # Numerical approximation
1876  self.writeln('const double y_save = rY[', i, '];')
1877  self.writeln('rY[', i, '] += delta;')
1878  self.writeln('const double temp = EvaluateYDerivative', i, '(', self.code_name(self.free_vars[0]), ', rY);')
1879  self.writeln('partialF = (temp-mEvalF[', i, '])/delta;')
1880  self.writeln('rY[', i, '] = y_save;')
1881  if self.jacobian_diagonal:
1882  self.close_block(blank_line=False)
1883  self.writeln('return partialF;')
1884  self.close_block()
1885 
1886  #Megan E. Marsh, Raymond J. Spiteri
1887  #Numerical Simulation Laboratory
1888  #University of Saskatchewan
1889  #December 2011
1890  #Partial support provided by research grants from
1891  #the National Science and Engineering Research
1892  #Council (NSERC) of Canada and the MITACS/Mprime
1893  #Canadian Network of Centres of Excellence.
1895  """Output the special methods needed for GRL1 style cell models.
1896 
1897  We generate:
1898  * UpdateTransmembranePotential update V_m
1899  * ComputeOneStepExceptVoltage does a GRL1 update for variables except voltage
1900  * EvaluateYDerivativeI for each variable I
1901  """
1903  ########################################################UpdateTransmembranePotential
1904  self.output_method_start('UpdateTransmembranePotential',
1905  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0])],
1906  'void', access='public')
1907  self.open_block()
1908  self.writeln('std::vector<double>& rY = rGetStateVariables();')
1909  self.writeln('unsigned v_index = GetVoltageIndex();')
1910  self.writeln('const double delta = 1e-8;')
1911  self.writeln()
1912  # Compute partial derivative of dV wrt V
1913  self.writeln(self.TYPE_DOUBLE, self.code_name(self.v_variable, ode=True), self.STMT_END)
1915  self.writeln()
1916  self.writeln('double evalF = ', self.code_name(self.v_variable, ode=True), self.STMT_END)
1917  self.writeln('mEvalF[', self.v_index, '] = ', self.code_name(self.v_variable, ode=True), self.STMT_END)
1918  self.writeln('double partialF = EvaluatePartialDerivative', self.v_index, '(', self.code_name(self.free_vars[0]), ', rY, delta, true);')
1919  self.writeln('if (fabs(partialF) < delta)')
1920  self.open_block()
1921  self.writeln('rY[v_index] += evalF*mDt;')
1922  self.close_block(False)
1923  self.writeln('else')
1924  self.open_block()
1925  self.writeln('rY[v_index] += (evalF/partialF)*(exp(partialF*mDt)-1.0);')
1926  self.close_block()
1927  self.close_block()
1928 
1929  #########################################################ComputeOneStepExceptVoltage
1930  self.output_method_start('ComputeOneStepExceptVoltage',
1931  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0])],
1932  'void', access='public')
1933  self.open_block()
1934  # Set up variables
1935  self.writeln('std::vector<double>& rY = rGetStateVariables();')
1936  self.writeln('const double delta = 1e-8;')
1937  self.writeln()
1938 
1939  # Evaluate RHS of equations (except dV/dt)
1940  non_v_vars = self.state_vars[:]
1941  if self.v_variable in non_v_vars:
1942  non_v_vars.remove(self.v_variable)
1943  self.output_derivative_calculations(non_v_vars)
1944 
1945  # Compute partial derivatives (for non-V)
1946  for i, var in enumerate(self.state_vars):
1947  if var is not self.v_variable:
1948  self.writeln('mEvalF[', i, '] = ', self.code_name(var, ode=True), self.STMT_END)
1949  self.writeln('mPartialF[', i, '] = EvaluatePartialDerivative', i, '(', self.code_name(self.free_vars[0]), ', rY, delta);')
1950 
1951  # Do the GRL updates
1952  for i, var in enumerate(self.state_vars):
1953  if var is not self.v_variable:
1954  self.open_block()
1955  self.writeln('if (fabs(mPartialF[', i, ']) < delta)')
1956  self.open_block()
1957  self.writeln('rY[', i, '] += mDt*', self.code_name(var, True), ';')
1958  self.close_block(False)
1959  self.writeln('else')
1960  self.open_block()
1961  self.writeln('rY[', i, '] += (', self.code_name(var, True), '/mPartialF[', i, '])*(exp(mPartialF[', i, ']*mDt)-1.0);')
1962  self.close_block()
1963  self.close_block()
1964  self.close_block()
1965 
1966  #########################################################Evaluate each equation
1967  for i, var in enumerate(self.state_vars):
1968  self.output_method_start('EvaluateYDerivative'+str(i),
1969  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
1970  'std::vector<double>& rY'],
1971  'double', access='public')
1972  self.open_block()
1973  if var is self.v_variable:
1974  self.writeln(self.TYPE_DOUBLE, self.code_name(self.v_variable, ode=True), self.STMT_END)
1976  self.writeln()
1977  self.writeln('return ', self.code_name(var, True), ';')
1978  self.close_block()
1979 
1980  self.output_grl_compute_partial(i, var)
1981 
1982  #Megan E. Marsh, Raymond J. Spiteri
1983  #Numerical Simulation Laboratory
1984  #University of Saskatchewan
1985  #December 2011
1986  #Partial support provided by research grants from
1987  #the National Science and Engineering Research
1988  #Council (NSERC) of Canada and the MITACS/Mprime
1989  #Canadian Network of Centres of Excellence.
1991  """Output the special methods needed for GRL2 style cell models.
1992 
1993  We generate:
1994  * Update TransmembranePotential update V_m
1995  * ComputeOneStepExceptVoltage does a GRL2 update for variables except voltage
1996  * EvaluateYDerivativeI for each variable I
1997  """
1999  ########################################################UpdateTransmembranePotential
2000  self.output_method_start('UpdateTransmembranePotential',
2001  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0])],
2002  'void', access='public')
2003  self.open_block()
2004  self.writeln('std::vector<double>& rY = rGetStateVariables();')
2005  self.writeln('const unsigned v_index = GetVoltageIndex();')
2006  self.writeln('const double delta = 1e-8;')
2007  self.writeln('const double yinit = rY[v_index];')
2008  self.writeln()
2009 
2010  # Do the first half step
2011  self.writeln(self.TYPE_DOUBLE, self.code_name(self.v_variable, ode=True), self.STMT_END)
2013  self.writeln()
2014  self.writeln('double evalF = ', self.code_name(self.v_variable, ode=True), self.STMT_END)
2015  self.writeln('mEvalF[', self.v_index, '] = ', self.code_name(self.v_variable, ode=True), self.STMT_END)
2016  self.writeln('double partialF = EvaluatePartialDerivative', self.v_index, '(', self.code_name(self.free_vars[0]), ', rY, delta, true);')
2017  self.writeln('if (fabs(partialF) < delta)')
2018  self.open_block()
2019  self.writeln('rY[v_index] += 0.5*evalF*mDt;')
2020  self.close_block(False)
2021  self.writeln('else')
2022  self.open_block()
2023  self.writeln('rY[v_index] += (evalF/partialF)*(exp(partialF*0.5*mDt)-1.0);')
2024  self.close_block()
2025 
2026  # Do the second half step
2027  self.writeln('rY[v_index] = yinit;')
2028  self.writeln('evalF = EvaluateYDerivative', self.v_index, '(', self.code_name(self.free_vars[0]), ', rY);')
2029  self.writeln('mEvalF[', self.v_index, '] = evalF;')
2030  self.writeln('partialF = EvaluatePartialDerivative', self.v_index, '(', self.code_name(self.free_vars[0]), ', rY, delta, true);')
2031  self.writeln('if (fabs(partialF) < delta)')
2032  self.open_block()
2033  self.writeln('rY[v_index] = yinit + evalF*mDt;')
2034  self.close_block(False)
2035  self.writeln('else')
2036  self.open_block()
2037  self.writeln('rY[v_index] = yinit + (evalF/partialF)*(exp(partialF*mDt)-1.0);')
2038  self.close_block()
2039  self.close_block() # End method
2040 
2041  #########################################################ComputeOneStepExceptVoltage
2042  self.output_method_start('ComputeOneStepExceptVoltage',
2043  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0])],
2044  'void', access='public')
2045  self.open_block()
2046  # Set up variables
2047  self.writeln('std::vector<double>& rY = rGetStateVariables();')
2048  self.writeln('const double delta=1e-8;')
2049  self.writeln('const unsigned size = GetNumberOfStateVariables();')
2050  self.writeln('mYInit = rY;')
2051  self.writeln('double y_save;')
2052  self.writeln()
2053 
2054  # Calculate partial derivatives
2056  for i, var in enumerate(self.state_vars):
2057  self.writeln(self.vector_index('mEvalF', i), self.EQ_ASSIGN, self.code_name(var, True), self.STMT_END)
2058  self.writeln()
2059  for i, var in enumerate(self.state_vars):
2060  if var is not self.v_variable:
2061  self.writeln('mPartialF[', i, '] = EvaluatePartialDerivative', i, '(', self.code_name(self.free_vars[0]), ', rY, delta);')
2062 
2063  # Update all variables
2064  self.writeln('for (unsigned var=0; var<size; var++)')
2065  self.open_block()
2066  self.writeln('if (var == ', self.v_index, ') continue;')
2067  self.writeln('if (fabs(mPartialF[var]) < delta)')
2068  self.open_block()
2069  self.writeln('rY[var] = mYInit[var] + 0.5*mDt*mEvalF[var];')
2070  self.close_block(False)
2071  self.writeln('else')
2072  self.open_block()
2073  self.writeln('rY[var] = mYInit[var] + (mEvalF[var]/mPartialF[var])*(exp(mPartialF[var]*0.5*mDt)-1.0);')
2074  self.close_block()
2075  self.close_block()
2076  self.writeln()
2077 
2078  # Determine new partial derivatives
2079  for i, var in enumerate(self.state_vars):
2080  if var is not self.v_variable:
2081  self.writeln()
2082  self.writeln('y_save = rY[', i, '];')
2083  self.writeln('rY[', i, '] = mYInit[', i, '];')
2084  self.writeln('mEvalF[', i, '] = EvaluateYDerivative', i, '(', self.code_name(self.free_vars[0]), ', rY);')
2085  self.writeln('mPartialF[', i, '] = EvaluatePartialDerivative', i, '(', self.code_name(self.free_vars[0]), ', rY, delta);')
2086  self.writeln('rY[', i, '] = y_save;')
2087 
2088  # Update all variables
2089  self.writeln('for (unsigned var=0; var<size; var++)')
2090  self.open_block()
2091  self.writeln('if (var == ', self.v_index, ') continue;')
2092  self.writeln('if (fabs(mPartialF[var]) < delta)')
2093  self.open_block()
2094  self.writeln('rY[var] = mYInit[var] + mDt*mEvalF[var];')
2095  self.close_block(False)
2096  self.writeln('else')
2097  self.open_block()
2098  self.writeln('rY[var] = mYInit[var] + (mEvalF[var]/mPartialF[var])*(exp(mPartialF[var]*mDt)-1.0);')
2099  self.close_block()
2100  self.close_block()
2101  self.writeln()
2102  self.close_block() # End method
2103 
2104  #########################################################Evaluate each equation
2105  for i, var in enumerate(self.state_vars):
2106  self.output_method_start('EvaluateYDerivative'+str(i),
2107  [self.TYPE_DOUBLE + self.code_name(self.free_vars[0]),
2108  'std::vector<double>& rY'],
2109  'double', access='public')
2110  self.open_block()
2111  if var is self.v_variable:
2112  self.writeln(self.TYPE_DOUBLE, self.code_name(self.v_variable, ode=True), self.STMT_END)
2114  self.writeln()
2115  self.writeln('return '+self.code_name(var, True)+';')
2116  self.close_block()
2117 
2118  self.output_grl_compute_partial(i, var)
2119 
2120 
2122  """Output any named model attributes defined in metadata.
2123 
2124  Such attributes are given by compound RDF annotations:
2125  model --pycml:named-attribute--> bnode
2126  bnode --pycml:name--> Literal(Attribute name, string)
2127  bnode --pycml:value--> Literal(Attribute value, double)
2128  """
2129  model = self.model
2130  meta_id = model.cmeta_id
2131  attrs = []
2132  if meta_id:
2133  property = cellml_metadata.create_rdf_node(('pycml:named-attribute', NSS['pycml']))
2134  name_prop = cellml_metadata.create_rdf_node(('pycml:name', NSS['pycml']))
2135  value_prop = cellml_metadata.create_rdf_node(('pycml:value', NSS['pycml']))
2136  source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
2137  attr_nodes = cellml_metadata.get_targets(model, source, property)
2138  for node in attr_nodes:
2139  name = cellml_metadata.get_target(model, node, name_prop)
2140  value = cellml_metadata.get_target(model, node, value_prop)
2141  attrs.append((name, value))
2142  for name, value in attrs:
2143  self.writeln('this->mAttributes["', name, '"] = ', value, ';')
2144  if attrs:
2145  self.writeln()
2146 
2148  """Output bottom boilerplate.
2149 
2150  End class definition, output ODE system information (to .cpp) and
2151  serialization code (to .hpp), and end the file.
2152  """
2153  # End main class
2154  self.set_indent(offset=0)
2155  # self.writeln_hpp('};\n\n')
2156  # ODE system information
2157 
2158  self.writeln(self.TYPE_VOID, self.class_name, '::v_GenerateSummary(SummaryList& s)')
2159  self.open_block()
2160  self.writeln('SolverUtils::AddSummaryItem(s, "', 'Cell model' , '", "', self.class_name , '");')
2161  self.close_block()
2162 
2163  #self.writeln('template<>')
2164  self.writeln(self.TYPE_VOID, self.class_name,
2165  '::v_SetInitialConditions()')
2166  self.open_block()
2167  # self.writeln('this->mSystemName', self.EQ_ASSIGN, '"', self.model.name, '"', self.STMT_END)
2168  # self.writeln('this->mFreeVariableName', self.EQ_ASSIGN,
2169  # '"', self.var_display_name(self.free_vars[0]), '"', self.STMT_END)
2170  # self.writeln('this->mFreeVariableUnits', self.EQ_ASSIGN,
2171  # '"', self.free_vars[0].units, '"', self.STMT_END)
2172  # self.writeln()
2173  def output_var(vector, var):
2174  self.writeln('this->m', vector, 'Names.push_back("', self.var_display_name(var), '");')
2175  self.writeln('this->m', vector, 'Units.push_back("', var.units, '");')
2176 
2177  for i,var in enumerate(self.state_vars):
2178  #output_var('Variable', var)
2179  init_val = getattr(var, u'initial_value', None)
2180 
2181  if init_val is None:
2182  init_comm = ' // Value not given in model'
2183  # Don't want compiler error, but shouldn't be a real number
2184  init_val = self.NOT_A_NUMBER
2185  else:
2186  init_comm = ''
2187  #21
2188  self.writeln('Vmath::Fill(m_nq, ', float(init_val), ',' , ' '*(21-len(init_val)) , 'm_cellSol[', i ,'], 1);',
2189  init_comm)
2190 
2191  # Model parameters
2192  for var in self.cell_parameters:
2193  if var.get_type() == VarTypes.Constant:
2194  output_var('Parameter', var)
2195  self.writeln()
2196  # Derived quantities
2197  for var in self.derived_quantities:
2198  output_var('DerivedQuantity', var)
2199  self.writeln()
2201  #self.writeln('this->mInitialised = true;')
2202  self.writeln('}',indent_level=1)
2203  #self.writeln()
2204  # Serialization
2205  # if self.include_serialization:
2206  # self.output_comment('Needs to be included last', subsidiary=True)
2207  # self.writeln_hpp('#include "SerializationExportWrapper.hpp"')
2208  # self.writeln_hpp('CHASTE_CLASS_EXPORT(', self.class_name, ')')
2209  # self.output_comment('Serialization for Boost >= 1.36')
2210  # self.writeln('#include "SerializationExportWrapperForCpp.hpp"')
2211  # self.writeln('CHASTE_CLASS_EXPORT(', self.class_name, ')')
2212  # self.writeln_hpp()
2213  # self.writeln_hpp('namespace boost')
2214  # self.open_block(subsidiary=True)
2215  # self.writeln_hpp('namespace serialization')
2216  # self.open_block(subsidiary=True)
2217  # # Save
2218  # self.writeln_hpp('template<class Archive>')
2219  # self.writeln_hpp('inline void save_construct_data(')
2220  # self.writeln_hpp('Archive & ar, const ', self.class_name,
2221  # ' * t, const unsigned int fileVersion)',
2222  # indent_offset=1)
2223  # self.open_block(subsidiary=True)
2224  # self.writeln_hpp('const boost::shared_ptr<AbstractIvpOdeSolver> p_solver = t->GetSolver();')
2225  # self.writeln_hpp('const boost::shared_ptr<AbstractStimulusFunction> p_stimulus = t->GetStimulusFunction();')
2226  # self.writeln_hpp('ar << p_solver;')
2227  # self.writeln_hpp('ar << p_stimulus;')
2228  # self.close_block(subsidiary=True)
2229  # # Load
2230  # self.writeln_hpp('template<class Archive>')
2231  # self.writeln_hpp('inline void load_construct_data(')
2232  # self.writeln_hpp('Archive & ar, ', self.class_name,
2233  # ' * t, const unsigned int fileVersion)',
2234  # indent_offset=1)
2235  # self.open_block(subsidiary=True)
2236  # self.writeln_hpp('boost::shared_ptr<AbstractIvpOdeSolver> p_solver;')
2237  # self.writeln_hpp('boost::shared_ptr<AbstractStimulusFunction> p_stimulus;')
2238  # self.writeln_hpp('ar >> p_solver;')
2239  # self.writeln_hpp('ar >> p_stimulus;')
2240  # self.writeln_hpp('::new(t)', self.class_name, '(p_solver, p_stimulus);')
2241  # self.close_block(subsidiary=True)
2242  # self.close_block(subsidiary=True)
2243  # self.close_block(subsidiary=True)
2244  # if self.dynamically_loadable:
2245  # # Write the C function to create instances of this cell model
2246  # self.writeln('extern "C"')
2247  # self.open_block()
2248  # self.writeln('AbstractCardiacCellInterface* MakeCardiacCell(')
2249  # self.writeln('boost::shared_ptr<AbstractIvpOdeSolver> pSolver,', indent_offset=2)
2250  # self.writeln('boost::shared_ptr<AbstractStimulusFunction> pStimulus)', indent_offset=2)
2251  # self.open_block()
2252  # self.writeln('return new ', self.class_name, '(pSolver, pStimulus);')
2253  # self.close_block()
2254  # self.close_block()
2255  # End file
2256  #self.writeln_hpp('#endif // ', self.include_guard)
2257  self.writeln('}', indent_level=0)
2258  return
2259 
2260  def output_lhs(self, expr):
2261  """Output the left hand side of an assignment expression."""
2262  if expr.localName == 'ci':
2263  self.output_variable(expr)
2264  elif expr.operator().localName == 'diff':
2265  ci_elt = expr.operands().next()
2266  self.output_variable(ci_elt, ode=True)
2267  return
2268 
2269  def output_variable(self, ci_elt, ode=False):
2270  """Output a ci element, i.e. a variable lookup."""
2271  if hasattr(ci_elt, '_cml_variable') and ci_elt._cml_variable:
2272  self.write(self.code_name(ci_elt.variable, ode=ode))
2273  else:
2274  # This ci element doesn't have all the extra annotations. It is a fully
2275  # qualified name though. This is typically because PE has been done.
2276  prefix = ['var_', 'd_dt_'][ode]
2277  varname = unicode(ci_elt)
2278  try:
2279  var = self.varobj(varname)
2280  except KeyError:
2281  var = None
2282  if var:
2283  self.write(self.code_name(var, ode=ode))
2284  else:
2285  # Assume it's a suitable name
2286  self.write(prefix + varname)
2287  return
2288 
2289  def output_function(self, func_name, args, *posargs, **kwargs):
2290  """Override base class method for special case of abs with 2 arguments.
2291 
2292  This comes from Maple's Jacobians, and should generate signum of the second argument.
2293  """
2294  args = list(args)
2295  if func_name == 'fabs' and len(args) == 2:
2296  super(CellMLToNektarTranslator, self).output_function('Signum', [args[1]], *posargs, **kwargs)
2297  else:
2298  super(CellMLToNektarTranslator, self).output_function(func_name, args, *posargs, **kwargs)
2299 
2300  @staticmethod
2302  """
2303  Return a list of units objects that give the possibilities for the dimensions
2304  of transmembrane ionic currents.
2305  """
2306  chaste_units = cellml_units.create_new(
2307  model, 'uA_per_cm2',
2308  [{'units': 'ampere', 'prefix': 'micro'},
2309  {'units': 'metre', 'prefix': 'centi', 'exponent': '-2'}])
2310  microamps = cellml_units.create_new(model, u'microamps',
2311  [{'units':'ampere', 'prefix':'micro'}])
2312  A_per_F = cellml_units.create_new(model, 'A_per_F',
2313  [{'units': 'ampere'},
2314  {'units': 'farad', 'exponent': '-1'}])
2315  return [chaste_units, microamps, A_per_F]
2316 
2317  # Name in CellML for the variable representing Chaste's membrane capacitance
2318  MEMBRANE_CAPACITANCE_NAME = u'chaste_membrane_capacitance'
2319 
2320  # Name of the component added to interface the model to Chaste
2321  INTERFACE_COMPONENT_NAME = u'chaste_interface'
2322 
2323  @staticmethod
2324  def add_special_conversions(converter, comp):
2325  """Add special units conversions for ionic currents.
2326 
2327  Adds conversions for the two other common conventions to/from the units expected by Chaste,
2328  uA/cm^2. The cases are:
2329 
2330  1. Current in amps/farads.
2331  In this case we convert to uA/uF then multiply by Chaste's value
2332  for the membrane capacitance (in uF/cm^2).
2333  2. Current in amps, capacitance in farads.
2334  We assume the cell model conceptually represents a cell, and hence
2335  that its membrane capacitance is supposed to represent the same
2336  thing as Chaste's. Thus convert current to uA, capacitance to uF,
2337  and return current/capacitance * Chaste's capacitance.
2338 
2339  comp is a component to which we should add any necessary variables, i.e. Chaste's capacitance.
2340  """
2341  klass = CellMLToNektarTranslator
2342  model = converter.model
2343  # Variables needed by some conversions
2344  model_Cm = model.get_config('Cm_variable')
2345  uF_per_cm2 = cellml_units.create_new(model, 'uF_per_cm2',
2346  [{'units': 'farad', 'prefix': 'micro'},
2347  {'units': 'metre', 'prefix': 'centi', 'exponent': '-2'}])
2348  Chaste_Cm = converter.add_variable(comp, klass.MEMBRANE_CAPACITANCE_NAME, uF_per_cm2)
2349  model._cml_Chaste_Cm = Chaste_Cm # Record for use in code_name
2350  # Add the conversions
2351  chaste_units, microamps, A_per_F = klass.get_current_units_options(model)
2352  converter.add_special_conversion(A_per_F, chaste_units,
2353  lambda expr: converter.times_rhs_by(expr, Chaste_Cm))
2354  converter.add_special_conversion(chaste_units, A_per_F,
2355  lambda expr: converter.divide_rhs_by(expr, Chaste_Cm))
2356  if model_Cm:
2357  converter.add_special_conversion(microamps, chaste_units,
2358  lambda expr: converter.times_rhs_by(converter.divide_rhs_by(expr, model_Cm),
2359  Chaste_Cm))
2360  converter.add_special_conversion(chaste_units, microamps,
2361  lambda expr: converter.divide_rhs_by(converter.times_rhs_by(expr, model_Cm),
2362  Chaste_Cm))
2363 
2364  @staticmethod
2365  def generate_interface(doc, solver_info):
2366  """Generate an interface component connecting the model to Chaste.
2367 
2368  On return from this method, Chaste code will only need to interact with variables in
2369  the new interface component. It will contain the transmembrane potential, the ionic
2370  and stimulus currents, the simulation time, and the derivatives.
2371 
2372  It may also contain other variables depending on the model, for example the intracellular
2373  calcium concentration (if annotated), modifiable parameters, and derived quantities.
2374 
2375  If the --convert-interfaces option has been supplied, units conversion will then be
2376  performed on this component, ensuring that all these variables are in the units expected
2377  by Chaste and linked by suitable conversions to the rest of the model.
2378 
2379  Note that if partial evaluation is then performed, the model will be collapsed into a
2380  single component. However, the interface will still be preserved in the correct units.
2381  """
2382  model = doc.model
2383  config = doc._cml_config
2384  klass = CellMLToNektarTranslator
2385  # Create Chaste units definitions
2386  ms = cellml_units.create_new(model, 'millisecond',
2387  [{'units': 'second', 'prefix': 'milli'}])
2388  mV = cellml_units.create_new(model, 'millivolt',
2389  [{'units': 'volt', 'prefix': 'milli'}])
2390  current_units, microamps = klass.get_current_units_options(model)[0:2]
2391  # The interface generator
2392  generator = processors.InterfaceGenerator(model, name=klass.INTERFACE_COMPONENT_NAME)
2393  iface_comp = generator.get_interface_component()
2394  # In case we need to convert initial values, we create the units converter here
2395  if config.options.convert_interfaces:
2396  warn_only = not config.options.fully_automatic and config.options.warn_on_units_errors
2397  notifier = NotifyHandler(level=logging.WARNING)
2398  logging.getLogger('units-converter').addHandler(notifier)
2399  converter = processors.UnitsConverter(model, warn_only, show_xml_context_only=True)
2400  klass.add_special_conversions(converter, iface_comp)
2401  generator.set_units_converter(converter)
2402  # And now specify the interface
2403  t = model.find_free_vars()[0]
2404  if not ms.dimensionally_equivalent(t.get_units()):
2405  # Oops!
2406  raise TranslationError('Time does not have dimensions of time')
2407  generator.add_input(t, ms)
2408  if doc.model.get_option('backward_euler'):
2409  # Backward Euler code generation requires access to the time step
2410  model_dt = solver_info.create_dt(generator, t.component, t.get_units())
2411  config.dt_variable = generator.add_input(model_dt, ms)
2412  config.dt_variable.set_pe_keep(True)
2413  elif doc.model.get_option('maple_output'):
2414  # CVODE Jacobians need to be able to scale for time too
2415  fake_dt = generator.add_variable(t.component, 'fake_dt', ms, initial_value='1.0')
2416  fake_dt._set_type(VarTypes.Constant)
2417  config.dt_variable = generator.add_input(fake_dt, t.get_units())
2418  config.dt_variable.set_is_modifiable_parameter(False)
2419  config.dt_variable.set_pe_keep(True)
2420 
2421  if config.options.use_chaste_stimulus and config.i_stim_var:
2422  # We need to make it a constant so add_input doesn't complain, then make it computed
2423  # again so that exposing metadata-annotated variables doesn't make it a parameter!
2424  generator.make_var_constant(config.i_stim_var, 0)
2425  config.i_stim_var = generator.add_input(config.i_stim_var, current_units,
2426  annotate=False, convert_initial_value=False)
2427  generator.make_var_computed_constant(config.i_stim_var, 0)
2428  # Also convert variables that make up the default stimulus
2429  # Note: we vary in/out-put primarily to test units conversion of initial values
2430  def add_oxmeta_ioput(oxmeta_name, units, inout):
2431  var = doc.model.get_variable_by_oxmeta_name(oxmeta_name, throw=False)
2432  if var:
2433  meth = getattr(generator, 'add_%sput' % inout)
2434  newvar = meth(var, units, annotate=False)
2435  newvar.set_pe_keep(True)
2436  for n in ['duration', 'period', 'offset', 'end']:
2437  add_oxmeta_ioput('membrane_stimulus_current_'+n, ms, 'in')
2438  add_oxmeta_ioput('membrane_stimulus_current_amplitude', current_units, 'out')
2439 
2440  if config.V_variable:
2441  config.V_variable = generator.add_input(config.V_variable, mV)
2442  ionic_vars = config.i_ionic_vars
2443  if ionic_vars:
2444  i_ionic = generator.add_output_function('i_ionic', 'plus', ionic_vars, current_units)
2445  config.i_ionic_vars = [i_ionic]
2446 
2447  if doc.model.get_option('use_data_clamp'):
2448  assert config.V_variable and ionic_vars
2449  # Create g_clamp
2450  conductance_units = current_units.quotient(mV).simplify()
2451  i_data_clamp_conductance = generator.add_variable(iface_comp, 'membrane_data_clamp_current_conductance', conductance_units, initial_value='0.0')
2452  i_data_clamp_conductance._set_type(VarTypes.Constant)
2453  i_data_clamp_conductance.set_pe_keep(True) # This prevents it becoming 'chaste_interface__membrane_data_clamp_current_conductance'
2454  config.i_data_clamp_conductance = generator.add_input(i_data_clamp_conductance, conductance_units)
2455  # Create V_clamp
2456  data_var = config.i_data_clamp_data = generator.add_variable(iface_comp, 'experimental_data_voltage', mV, initial_value='0.0')
2457  data_var._set_type(VarTypes.Constant)
2458  data_var.set_pe_keep(True)
2459  data_var._cml_code_name = 'GetExperimentalVoltageAtTimeT(%(time)s)'
2460  # Create the current: I = g_clamp * (V - V_clamp)
2461  current_var = config.i_data_clamp_current = generator.add_variable(iface_comp, 'membrane_data_clamp_current', current_units)
2462  current_var._set_type(VarTypes.Computed)
2463  current_var.set_is_derived_quantity(True)
2464  sub = mathml_apply.create_new(model, u'minus', [config.V_variable.name, data_var.name])
2465  times = mathml_apply.create_new(model, u'times', [config.i_data_clamp_conductance.name, sub])
2466  assign = mathml_apply.create_new(model, u'eq', [current_var.name, times])
2467  generator.add_expr_to_comp(iface_comp, assign)
2468  # Make dV/dt use the new current
2469  def process_ci(elt):
2470  # Add reference to new current after first existing ionic current
2471  ref = mathml_ci.create_new(model, local_current_var.name)
2472  elt.xml_parent.xml_insert_after(elt, ref)
2473  if hasattr(ionic_vars[0], '_cml_ref_in_dvdt'):
2474  local_current_var = generator.connect_variables(current_var, (ionic_vars[0]._cml_ref_in_dvdt.component.name, current_var.name))
2475  process_ci(ionic_vars[0]._cml_ref_in_dvdt)
2476  else:
2477  dVdt = config.V_variable.get_all_expr_dependencies()[0]
2478  local_current_var = generator.connect_variables(current_var, (config.V_variable.component.name, current_var.name))
2479  def process_ci_elts(elt):
2480  """Recursively process any ci elements in the tree rooted at elt."""
2481  if isinstance(elt, mathml_ci):
2482  if elt.variable is ionic_vars[0]:
2483  process_ci(elt)
2484  else:
2485  for child in getattr(elt, 'xml_children', []):
2486  process_ci_elts(child)
2487  process_ci_elts(dVdt)
2488 
2489  # Finish up
2490  def errh(errors):
2491  raise TranslationError("Creation of Chaste interface component failed:\n " + str(errors))
2492  generator.finalize(errh, check_units=False)
2493  # Apply units conversions just to the interface, if desired
2494  if config.options.convert_interfaces:
2495  converter.add_conversions_for_component(iface_comp)
2496  converter.finalize(errh, check_units=False)
2497  notifier.flush()
2498  logging.getLogger('units-converter').removeHandler(notifier)
2499  if notifier.messages:
2500  msg = 'Problems occurred converting model variables to Chaste units.\n'
2501  if ionic_vars and ionic_vars[0].get_units().dimensionally_equivalent(microamps):
2502  msg += 'To convert the ionic currents for this model, '\
2503  'the model membrane capacitance needs to be identified.'
2504  if config.options.fully_automatic:
2505  raise TranslationError(msg)
2506  else:
2507  print >>sys.stderr, msg
string STMT_END
Various language tokens #.
Definition: translators.py:141
def calculate_extended_dependencies
Dependency related methods #.
Definition: translators.py:899
def dimensionally_equivalent
Definition: pycml.py:3185
InputIterator find(InputIterator first, InputIterator last, InputIterator startingpoint, const EqualityComparable &value)
Definition: StdRegions.hpp:316