Nektar++
CellMLToNektarTranslator.py
Go to the documentation of this file.
2#Importing various functions used by this class (i have outcommented modules that we don't need)
3import os
4import optparse
5import re
6import time
7import sys
8from cStringIO import StringIO
9
10# Common CellML processing stuff below
11import pycml
12from pycml import * # Put contents in the local namespace as well
13import optimize
14import processors
15import validator
16
17#Importing functions from modified_translators.py
18import 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
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'
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',
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_namecode_name(self.free_vars[0]))
246 # Output equations
247 self.output_comment('Mathematics')
248 self.output_equationsoutput_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_namecode_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('std::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
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
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']):
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', [], 'std::shared_ptr<RegularStimulus>', 'public')
413 self.open_block()
414 self.output_comment('Use the default stimulus specified by CellML metadata')
416 self.writeln('std::shared_ptr<RegularStimulus> p_cellml_stim(new RegularStimulus(')
417 self.writeln(' -fabs(', self.code_namecode_name(vars['amplitude']), '),')
418 self.writeln(' ', self.code_namecode_name(vars['duration']), ',')
419 self.writeln(' ', self.code_namecode_name(vars['period']), ',')
420 if vars['offset']:
421 self.writeln(' ', self.code_namecode_name(vars['offset']))
422 else:
423 self.writeln(' 0.0')
424 if vars['end']:
425 self.writeln(' , ', self.code_namecode_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_DOUBLETYPE_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_namecode_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 = 'std::shared_ptr<AbstractIvpOdeSolver> /* unused; should be empty */'
489 solver2 = ''
490 #solver1 = solver2 = ''
491 else: #this currently outputs the boilerplate stuff
492 solver1 = 'std::shared_ptrALPHA<AbstractIvpOdeSolver> pSolverBRAVO'
493 solver2 = '"' + self.class_name + '"'
494
495 if self.use_lookup_tablesuse_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, 'std::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_namecode_name(self.free_vars[0]))
565 self.NODESETNODESET = 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.NODESETNODESET):
569 # self.writeln('test')
570 if isinstance(expr, cellml_variable):
571 codename = str(self.code_namecode_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
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
634 if self.v_indexv_index == -1:
635 return 'UNSIGNED_UNSET'
636 else:
637 return str(self.v_indexv_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_REFTYPE_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_namecode_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_namecode_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_tablesuse_lookup_tables and not self.separate_lut_class:
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_namecode_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()
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_checkingoutput_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_namecode_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_namecode_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
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()
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()
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_DOUBLETYPE_CONST_DOUBLE, self.code_namecode_name(self.config.dt_variable), ' = mDt;')
1008 # Hack: avoid unused variable warning
1009 self.writeln('double _unused = ', self.code_namecode_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_REFTYPE_VECTOR_REF == CellMLToNektarTranslator.TYPE_VECTOR_REF:
1060 self.writeln('if (!%s) %s = &rGetStateVariables();' % (pointer, pointer))
1061 self.writeln('const ', self.TYPE_VECTOR_REFTYPE_VECTOR_REF, 'rY = *', pointer, self.STMT_END)
1062 else:
1063 self.writeln(self.TYPE_VECTOR_REFTYPE_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_REFTYPE_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
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_namecode_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:
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_namecode_name(expr) + self.EQ_ASSIGN
1140 get_stim = 'GetIntracellularAreaStimulus(' + self.code_namecode_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_namecode_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
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_DOUBLETYPE_DOUBLE, self.code_namecode_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
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_namecode_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_DOUBLETYPE_CONST_DOUBLE, self.code_namecode_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_DOUBLETYPE_DOUBLE
1251 elif getattr(assigned_var, '_cml_modifiable', False):
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 """
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_DOUBLETYPE_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_equationsoutput_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_DOUBLETYPE_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
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_namecode_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_namecode_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_namecode_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 """
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_namecode_name(self.free_vars[0]))
1524 self.output_comment('Mathematics')
1525 #907: Declare dV/dt
1526 if dvdt:
1527 self.writeln(self.TYPE_DOUBLETYPE_DOUBLE, self.code_namecode_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.NODESETNODESET = nonv_nodeset - table_index_nodes_used
1531 self.output_equationsoutput_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_DOUBLETYPE_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_equationsoutput_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',
1586 self.TYPE_CONST_DOUBLETYPE_CONST_DOUBLE + 'rCurrentGuess' + argsize,
1587 self.TYPE_DOUBLETYPE_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_namecode_name(self.free_vars[0]))
1596 self.output_equationsoutput_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_namecode_name(var, ode=True), self.STMT_END)
1607 self.close_block()
1608
1609 # Jacobian
1610 ##########
1611 self.output_method_start('ComputeJacobian',
1613 self.TYPE_CONST_DOUBLETYPE_CONST_DOUBLE + 'rCurrentGuess' + argsize,
1614 self.TYPE_DOUBLETYPE_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_DOUBLETYPE_CONST_DOUBLE, self.code_namecode_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_namecode_name(self.free_vars[0]))
1626 self.output_equationsoutput_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',
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_indexv_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_namecode_name(self.free_vars[0]))
1662 self.output_equationsoutput_equations(nodeset - table_index_nodes_used)
1663 # Update V
1664 self.writeln()
1665 self.writeln('rY[', self.v_indexv_index, '] += ', dt_name, '*',
1666 self.code_namecode_name(self.state_vars[self.v_indexv_index], ode=True), self.STMT_END)
1667 self.close_block()
1668
1669 # ComputeOneStepExceptVoltage
1670 #############################
1671 self.output_method_start('ComputeOneStepExceptVoltage',
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_DOUBLETYPE_CONST_DOUBLE, self.code_namecode_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_namecode_name(self.free_vars[0]))
1696 self.output_equationsoutput_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_namecode_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',
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_namecode_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_DOUBLETYPE_CONST_DOUBLE, 'tau_inv = rAlphaOrTau[', i, '] + rBetaOrInf[', i, '];')
1791 self.writeln(self.TYPE_CONST_DOUBLETYPE_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_namecode_name(self.free_vars[0]))
1826 self.output_comment('Mathematics')
1827 self.output_equationsoutput_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
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),
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_namecode_name(self.free_vars[0]))
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_namecode_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',
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_DOUBLETYPE_DOUBLE, self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
1915 self.writeln()
1916 self.writeln('double evalF = ', self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
1917 self.writeln('mEvalF[', self.v_indexv_index, '] = ', self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
1918 self.writeln('double partialF = EvaluatePartialDerivative', self.v_indexv_index, '(', self.code_namecode_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',
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_namecode_name(var, ode=True), self.STMT_END)
1949 self.writeln('mPartialF[', i, '] = EvaluatePartialDerivative', i, '(', self.code_namecode_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_namecode_name(var, True), ';')
1958 self.close_block(False)
1959 self.writeln('else')
1960 self.open_block()
1961 self.writeln('rY[', i, '] += (', self.code_namecode_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),
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_DOUBLETYPE_DOUBLE, self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
1976 self.writeln()
1977 self.writeln('return ', self.code_namecode_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',
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_DOUBLETYPE_DOUBLE, self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
2013 self.writeln()
2014 self.writeln('double evalF = ', self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
2015 self.writeln('mEvalF[', self.v_indexv_index, '] = ', self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
2016 self.writeln('double partialF = EvaluatePartialDerivative', self.v_indexv_index, '(', self.code_namecode_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_indexv_index, '(', self.code_namecode_name(self.free_vars[0]), ', rY);')
2029 self.writeln('mEvalF[', self.v_indexv_index, '] = evalF;')
2030 self.writeln('partialF = EvaluatePartialDerivative', self.v_indexv_index, '(', self.code_namecode_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',
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_namecode_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_namecode_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_indexv_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_namecode_name(self.free_vars[0]), ', rY);')
2085 self.writeln('mPartialF[', i, '] = EvaluatePartialDerivative', i, '(', self.code_namecode_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_indexv_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),
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_DOUBLETYPE_DOUBLE, self.code_namecode_name(self.v_variable, ode=True), self.STMT_END)
2114 self.writeln()
2115 self.writeln('return '+self.code_namecode_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 std::shared_ptr<AbstractIvpOdeSolver> p_solver = t->GetSolver();')
2225 # self.writeln_hpp('const std::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('std::shared_ptr<AbstractIvpOdeSolver> p_solver;')
2237 # self.writeln_hpp('std::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('std::shared_ptr<AbstractIvpOdeSolver> pSolver,', indent_offset=2)
2250 # self.writeln('std::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':
2264 elif expr.operator().localName == 'diff':
2265 ci_elt = expr.operands().next()
2266 self.output_variableoutput_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_namecode_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_namecode_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
def output_evaluate_y_derivatives(self, method_name='EvaluateYDerivatives')
def output_derivative_calculations_grl(self, var, assign_rY=False, extra_nodes=set(), extra_table_nodes=set())
def output_state_assignments(self, exclude_nonlinear=False, assign_rY=True, nodeset=None, pointer='')
def output_method_start(self, method_name, args, ret_type, access=None, defaults=[])
def output_derivative_calculations(self, state_vars, assign_rY=False, extra_nodes=set(), extra_table_nodes=set())
def output_variable(self, ci_elt, ode=False)
Definition: translators.py:642
def set_indent(self, level=None, offset=None)
Definition: translators.py:431
def output_comment(self, *args, **kwargs)
Definition: translators.py:416
def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[])
Dependency related methods #.
Definition: translators.py:899
def code_name(self, var, ode=False, prefix=None)
Definition: translators.py:442
def output_doxygen(self, *args, **kwargs)
Definition: translators.py:426
def writeln(self, *args, **kwargs)
Definition: translators.py:364
def output_table_index_generation_code(self, key, idx)
def lut_factor(self, idx, include_comma=False, include_type=False)
def output_lut_generation(self, only_index=None)
def close_block(self, blank_line=True, **kwargs)
Definition: translators.py:885
def output_lut_deletion(self, only_index=None)
string STMT_END
Various language tokens #.
Definition: translators.py:141
def output_table_index_generation(self, time_name, nodeset=set())
def simplify(self, other_units=None, other_exponent=1)
Definition: pycml.py:2967
def dimensionally_equivalent(self, other_units)
Definition: pycml.py:3185
def version_comment(note_time=True)
Definition: translators.py:62
def DEBUG(facility, *args)
Definition: utilities.py:95
InputIterator find(InputIterator first, InputIterator last, InputIterator startingpoint, const EqualityComparable &value)
Definition: StdRegions.hpp:475