8from cStringIO
import StringIO
23 As CellMLTranslator, but targets more recent Chaste style.
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.)
31 USES_SUBSIDIARY_FILE =
True
34 TYPE_VECTOR =
'std::vector<double> '
35 TYPE_VECTOR_REF =
'std::vector<double>& '
40 """Convenience wrapper for writing to the header file."""
41 kwargs[
'subsidiary'] =
True
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,
54 for key, default
in our_kwargs.iteritems():
55 setattr(self, key, kwargs.get(key, default))
63 return super(CellMLToNektarTranslator, self).
translate(*args, **kwargs)
66 """Set the LT method prefix (requires self.class_name to be set)."""
67 if self.separate_lut_class:
73 """Output the start of each output file.
76 the .hpp file,
and doxygen comment.
79 then includes that class' header instead of AbstractCardiacCell.
81 If self.dynamically_loadable is set, includes extra headers needed
89 from translators
import version_comment
90 for sub
in [
False,
True]:
92 'This source file was generated from CellML.\n\n',
93 'Model: ', self.
model.name,
'\n\n',
95 '\n\n<autogenerated>',
100 self.
writeln(
'#include <iostream>')
101 self.
writeln(
'#include <string>')
115 self.
writeln(
'namespace Nektar')
120 self.
writeln_hpp(
'#include "AbstractBackwardEulerCardiacCell.hpp"')
121 self.
writeln(
'#include "CardiacNewtonSolver.hpp"')
127 if not self.
doc._cml_rush_larsen:
128 self.
writeln(
'#include "Warnings.hpp"')
142 self.
writeln_hpp(
'#include "AbstractCardiacCellWithModifiers.hpp"')
143 self.
writeln_hpp(
'#include "AbstractModifier.hpp"')
147 if self.dynamically_loadable:
148 self.
writeln_hpp(
'#include "AbstractDynamicallyLoadableEntity.hpp"')
150 if self.use_protocol:
151 self.
writeln_hpp(
'#include "AbstractTemplatedSystemWithOutputs.hpp"')
165 self.
writeln_hpp(
'#include <CardiacEPSolver/CellModels/CellModel.h>')
171 """Set the access specification for subsequent output.
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.
183 """Output the start of a method declaration/definition.
185 Will write to both the .hpp and .cpp file.
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.
191 DEBUG('translator',
'Generating code for method', method_name)
195 if ret_type[-1] !=
' ':
196 ret_type = ret_type +
' '
199 args_string_cpp =
', '.join(filter(
None, map(str, args)))
201 assert len(defaults) == len(args)
202 args_with_default = []
203 for (arg, default)
in zip(map(str, args), map(str, defaults)):
206 args_with_default.append(arg +
'=' + default)
208 args_with_default.append(arg)
209 args_string_hpp =
', '.join(args_with_default)
211 args_string_hpp = args_string_cpp
213 self.
writeln(ret_type, self.
class_name,
'::', method_name,
'(', args_string_cpp,
')')
216 """Output a ComputeDerivedQuantities method if any such quantities exist.
218 Looks for variables annotated
with pycml:derived-quantity=yes,
and generates
219 a method to compute all these variables
from a given state.
231 if self.use_chaste_stimulus:
232 i_stim = [self.
doc._cml_config.i_stim_var]
235 if self.use_data_clamp:
236 prune = [self.
config.i_data_clamp_data]
252 for i, var
in enumerate(dqs):
258 """This method outputs the boost serialize method for the
259 header files that need it."""
265 self.
writeln_hpp(
'/// Creates an instance of this class', indent_offset=1)
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)
274 if self.dynamically_loadable:
275 self.
writeln_hpp(
'archive & boost::serialization::base_object<AbstractDynamicallyLoadableEntity>(*this);')
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)
281 self.
writeln_hpp(
'/// Name of class',indent_offset=1)
282 self.
writeln_hpp(
'static std::string className;\n',indent_offset=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)
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)
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)
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)
310 """Output declarations, set & get methods for cell parameters.
312 Sets self.cell_parameters to be those constant variables annotated with
313 pycml:modifiable-parameter. These use the mParameters functionality
in
316 Also collects any variables annotated
with an RDF oxmeta name into
317 self.
metadata_vars. Only constants
and state variables are included.
321 lambda v: v.is_modifiable_parameter,
322 cellml_metadata.find_variables(self.
model,
323 (
'pycml:modifiable-parameter', NSS[
'pycml']),
331 var._cml_param_index = i
334 vars = cellml_metadata.find_variables(self.
model, (
'bqbiol:is', NSS[
u'bqbiol']))
336 vars = filter(
lambda v: v.oxmeta_name, vars)
338 self.
metadata_vars = set([v
for v
in vars
if v.get_type() != VarTypes.Free])
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']
352 self.
output_comment(
'\nSettable parameters and readable variables\n', subsidiary=
True)
357 self.
writeln_hpp(
'std::shared_ptr<AbstractModifier> mp_' + var.oxmeta_name +
'_modifier', self.
STMT_END)
366 if var.is_statically_const(ignore_annotations=
True):
377 var._cml_has_modifier =
True
385 (
'pycml:derived-quantity', NSS[
'pycml']),
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
398 * membrane_stimulus_current_offset
399 * membrane_stimulus_current_end
401 Ensures that the amplitude of the generated RegularStimulus
is negative.
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']):
412 self.
output_method_start(
'UseCellMLDefaultStimulus', [],
'std::shared_ptr<RegularStimulus>',
'public')
414 self.
output_comment(
'Use the default stimulus specified by CellML metadata')
416 self.
writeln(
'std::shared_ptr<RegularStimulus> p_cellml_stim(new RegularStimulus(')
433 If a (state) variable has been annotated as cytosolic_calcium_concentration,
434 generate a GetIntracellularCalciumConcentration method.
437 cai = self.
doc.model.get_variable_by_oxmeta_name(
'cytosolic_calcium_concentration', throw=
False)
447 Return the full name of var in a form suitable
for inclusion
in a source file.
449 Overrides the base
class version to access mParameters for parameters.
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'):
461 return super(CellMLToNektarTranslator, self).
code_name(var, *args, **kwargs)
465 """Output top boilerplate.
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.
477 assert hasattr(self.
model,
u'solver_info')
479 num_linear_odes = len(self.
model.solver_info.xml_xpath(
u'solver:linear_odes/m:math/m:apply'))
481 nonlinear_entries = self.
model.solver_info.xml_xpath(
u'solver:jacobian/solver:entry/@var_j')
488 solver1 =
'std::shared_ptr<AbstractIvpOdeSolver> /* unused; should be empty */'
492 solver1 =
'std::shared_ptrALPHA<AbstractIvpOdeSolver> pSolverBRAVO'
511 self.
output_constructor([solver1,
'std::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus'],
512 [solver2, self.
class_name +
'::create',
'"Description of the model?"'])
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)
525 self.
writeln(
'State Variables:')
529 self.
writeln(
'Free Variables:')
538 self.
writeln(
'm_nq = pField->GetNpoints();\n')
553 state_vars = self.
doc.model.find_state_vars()
554 derivs = set(map(
lambda v: (v, self.
free_vars[0]), state_vars))
562 all_nodes = nonv_nodeset|v_nodeset
563 extra_table_nodes=set()
570 if isinstance(expr, cellml_variable):
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)
591 concentration_vars = 0
598 var_actual = str(var)[33:str(var).rfind(
'__')]
603 if str(var).
find(
'membrane__V') != -1:
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):
608 self.
writeln(
'm_gates.push_back(',str(gate_vars),
');')
614 self.
writeln(
'm_concentrations.push_back(',str(gate_vars),
');')
635 return 'UNSIGNED_UNSET'
640 """Output the VerifyStateVariables method.
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.
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)
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.')
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.')
678 self.
writeln(
'const double tol = 100*this->mAbsTol;')
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:
685 additional_tolerance_adjustment =
' - tol'
686 self.
writeln(
'if (', self.
code_namecode_name(var),
' < ', var.get_rdf_annotation(low_prop), additional_tolerance_adjustment,
')')
691 for var
in high_range_vars:
693 additional_tolerance_adjustment =
' + tol'
694 self.
writeln(
'if (', self.
code_namecode_name(var),
' > ', var.get_rdf_annotation(high_prop), additional_tolerance_adjustment,
')')
705 """Output a cell constructor.
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
711 base_class_params
is a list of parameters to be supplied to the base
class
712 constructor. Entries will be converted to strings.
716 self.
writeln(
'= GetCellModelFactory().RegisterCreatorFunction(', indent_offset=2)
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 =
');'
724 self.
writeln(param, comma, indent_offset=3)
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.");')
746 self.
output_comment(
'We have a default stimulus specified in the CellML file metadata')
747 self.
writeln(
'this->mHasDefaultStimulusFromCellML = true', self.
STMT_END)
751 self.
output_comment(
'These will get initialised to DummyModifiers in the base class method.')
753 self.
writeln(
'this->AddModifier("' + var.oxmeta_name +
'",')
758 if var.get_type() == VarTypes.Constant:
764 if self.use_protocol:
765 outputs = cellml_metadata.find_variables(self.
model,
766 (
'pycml:output-variable', NSS[
'pycml']),
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:
773 elif output.is_derived_quantity:
775 elif output.is_modifiable_parameter:
778 raise ValueError(
'Unexpected protocol output: ' + str(output))
782 self.
writeln(
'this->mOutputsInfo.resize(', len(outputs),
');')
783 for i, output
in enumerate(outputs):
785 'std::make_pair(', nl=
False)
786 write_output_info(output)
789 outputs = set(outputs)
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)):
798 vector_outputs = cellml_metadata.find_variables(self.
model, prop, name)
799 assert len(vector_outputs) > 0
801 self.
writeln(
'this->mVectorOutputsInfo[', i,
'].resize(', len(vector_outputs),
');')
802 for j, output
in enumerate(vector_outputs):
804 'std::make_pair(', nl=
False)
805 write_output_info(output)
808 outputs.update(vector_outputs)
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):
818 self.
writeln(
'this->mNameMap["', alias,
'"] = "', name,
'";')
821 self.
writeln(
'ProcessOutputsInfo();')
824 inputs = cellml_metadata.find_variables(self.
model, (
'pycml:input-variable', NSS[
'pycml']),
'yes')
827 self.
writeln(
'this->mInputNames.reserve(', len(inputs),
');')
840 """Hook for subclasses to add further content to the constructor."""
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.
848 if self.use_lookup_tables:
849 if self.separate_lut_class:
850 self.output_method_start(
'GetLookupTableCollection', [],
'AbstractLookupTableCollection*')
852 self.writeln(
'return ', self.lt_class_name,
'::Instance();')
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)
862 """Get the bounds and step size for a particular table.
864 key should be a key into self.lookup_table_indices.
865 Returns (min, max, step) suitable for putting
in generated code.
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'])
874 """Output methods in the LT class for indexing the tables, and checking index bounds.
876 These will be methods like
877 const double * const IndexTable0(double index_var);
879 void IndexTable0(double index_var, unsigned& index, double& factor);
881 bool CheckIndex0(double& index_var);
882 for checking the bounds.
884 for key, idx
in self.
doc.lookup_table_indexes.iteritems():
886 method_name =
'IndexTable' + str(idx)
888 method =
'const double * %s(double %s)' % (method_name, varname)
891 idx_var =
'_table_index_' + str(idx)
893 factor =
', double& ' + factor
894 method =
'void %s(double %s, unsigned& %s%s)' % (method_name, varname, idx_var, factor)
899 self.
writeln(
'return _lt_', idx,
'_row;')
902 if self.
config.options.check_lt_bounds:
903 self.
writeln(
'#define COVERAGE_IGNORE', indent=
False)
904 self.
writeln(
'bool CheckIndex', idx,
'(double& ', varname,
')')
909 self.
writeln(
'#undef COVERAGE_IGNORE\n', indent=
False)
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:
918 '::Instance()->CheckIndex', idx,
'(', varname,
')', self.
STMT_END)
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:
927 method_name = self.
lt_class_name +
'::Instance()->IndexTable' + str(idx)
929 self.
writeln(
'const double* const _lt_', idx,
'_row = ', method_name,
'(', varname,
');')
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,
');')
938 """Output a separate class for lookup tables.
940 This will live entirely in the .cpp file.
"""
949 self.
writeln(
'if (mpInstance.get() == NULL)')
953 self.
writeln(
'return mpInstance.get();')
956 self.
writeln(
'void FreeMemory()')
959 self.
writeln(
'mNeedsRegeneration.assign(mNeedsRegeneration.size(), true);')
970 self.
writeln(
'protected:', indent_level=0)
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])
992 self.
writeln(
'mNumberOfTables[', idx,
'] = ', num_tables, self.
STMT_END)
995 self.
writeln(
'mTableStepInverses[', idx,
'] = ', str(1/float(step)), self.
STMT_END)
997 self.
writeln(
'mNeedsRegeneration[', idx,
'] = true;')
1002 self.
writeln(
'void RegenerateTables()')
1004 event_handler =
'AbstractLookupTableCollection::EventHandler::'
1005 self.
writeln(event_handler,
'BeginEvent(', event_handler,
'GENERATE_TABLES);')
1006 if self.
config.options.include_dt_in_tables:
1010 self.
writeln(
'_unused = _unused;\n')
1011 for idx
in self.
doc.lookup_table_indexes.itervalues():
1012 self.
writeln(
'if (mNeedsRegeneration[', idx,
'])')
1016 self.
writeln(
'mNeedsRegeneration[', idx,
'] = false;')
1018 self.
writeln(event_handler,
'EndEvent(', event_handler,
'GENERATE_TABLES);')
1021 self.
writeln(
'private:', indent_level=0)
1022 self.
writeln(
'/** The single instance of the class */')
1039 """Output statements extracting state variables from their vector.
1041 If exclude_nonlinear is set to true, state variables appearing
1042 in the nonlinear system will
not be included.
1044 If nodeset
is given, only state variables appearing
in nodeset
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>*.
1053 and (nodeset
is None or var
in nodeset)):
1055 if assign_rY
and used_vars:
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).')
1060 self.
writeln(
'if (!%s) %s = &rGetStateVariables();' % (pointer, pointer))
1064 self.
writeln(
'bool made_new_cvode_vector = false;')
1065 self.
writeln(
'if (!%s)' % (pointer))
1067 self.
writeln(
'rY = rGetStateVariables();')
1071 self.
writeln(
'made_new_cvode_vector = true;')
1072 self.
writeln(
'rY = MakeNVector(*%s);' % (pointer))
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)
1082 value =
'(%s %s %s ? %s : %s)' % (value, reln, prop_value, prop_value, value)
1085 if var
in used_vars:
1091 value = check_bound(low_prop,
'<', var, value)
1092 value = check_bound(high_prop,
'>', var, value)
1099 '; Initial value: ',
1100 getattr(var,
u'initial_value',
'Unknown'))
1106 """Return code for a call to a modifier function for an oxmeta-annotated variable.
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.
1111 return (
'mp_' + var.oxmeta_name +
'_modifier->Calc(' +
1115 """Return code for accessing the i'th index of vector."""
1116 return vector +
'[' + str(i) +
']'
1119 """Return code for creating a new vector with the given 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]))
1127 """Output assignments for nonlinear state variables."""
1129 if not nodeset
or var
in nodeset:
1137 """Return code for getting Chaste's stimulus current."""
1138 expr = self.
doc._cml_config.i_stim_var
1141 if self.
doc._cml_config.i_stim_negated:
1142 get_stim =
'-' + get_stim
1143 return output + get_stim + self.
STMT_END
1146 """Output the mathematics described by nodeset.
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.
1155 if self.
doc._cml_config.i_stim_var
in nodeset:
1157 i_stim = self.
doc._cml_config.i_stim_var
1162 for expr
in (e
for e
in self.
model.get_assignments()
if e
in nodeset):
1180 """Output an assignment statement.
1182 Has overrides for various special cases.
1185 writing_data_clamp_current =
False
1187 if isinstance(expr, cellml_variable):
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.)')
1197 self.
writeln(
'if (mDataClampIsOn)')
1207 has_modifier = self.
use_modifiers and getattr(assigned_var,
'_cml_has_modifier',
False)
1214 elif getattr(assigned_var,
'_cml_modifiable',
False):
1218 and assigned_var.get_type() != VarTypes.State):
1222 assigned_var._cml_has_modifier =
False
1224 assigned_var._cml_has_modifier =
True
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]
1251 elif getattr(assigned_var,
'_cml_modifiable',
False):
1254 if writing_data_clamp_current:
1260 """Output the mathematics in this model.
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
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.
1275 For other solvers, only 2 methods are needed:
1276 * EvaluateYDerivatives computes the RHS of the ODE system
1277 * GetIIonic
is as above
1279 Where derived-quantity annotations are present, we also generate a
1280 ComputeDerivedQuantities method.
1296 """Output the lookup table index calculations needed for the given equations, if tables are enabled.
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.
1301 Returns the subset of nodeset used
in calculating the indices.
1310 """Output the GetIIonic method."""
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)
1323 i_stim = self.
doc._cml_config.i_stim_var
1334 if self.
doc._cml_config.i_ionic_negated:
1335 self.
writeln(
'-(', nl=
False, indent=
False)
1337 for varelt
in self.
model.solver_info.ionic_current.var:
1338 if plus: self.
write(
'+')
1341 if self.
doc._cml_config.i_ionic_negated:
1342 self.
writeln(
')', nl=
False, indent=
False)
1344 """if self.TYPE_VECTOR_REF == CellMLToCvodeTranslator.TYPE_VECTOR_REF:
1345 self.writeln('if (made_new_cvode_vector)')
1347 self.
writeln(
'DeleteVector(rY);')
1349 self.writeln('EXCEPT_IF_NOT(!std::isnan(i_ionic));')
1357 """Output the EvaluateYDerivatives method."""
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)
1366 self.
writeln(
'for (unsigned int i = 0; i < m_nq; ++i)')
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:]
1393 if 'membrane__V' in var:
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')
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)
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:]
1419 if 'membrane__V' in str(var):
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')
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)
1444 m_gate_tau_counter = 0
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:]
1454 if 'membrane__V' in str(var):
1455 self.
writeln(
'outarray[',i,
'][i] = ' + str(var_name) +
';')
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
1465 self.
writeln(
'outarray[',i,
'][i] = ' + str(var_name) +
';')
1476 extra_table_nodes=set()):
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.
1487 derivs = set(map(
lambda v: (v, self.
free_vars[0]), state_vars))
1511 if self.use_data_clamp:
1512 prune = set([self.
config.i_data_clamp_data]) | nonv_nodeset
1514 prune = nonv_nodeset
1520 all_nodes = nonv_nodeset|v_nodeset
1570 return all_nodes | table_index_nodes_used
1573 """Output the mathematics methods used in a backward Euler cell.
1575 Outputs ComputeResidual, ComputeJacobian,
1576 UpdateTransmembranePotential and ComputeOneStepExceptVoltage.
1588 'void', access=
'public')
1605 self.
writeln(
'rResidual[', j,
'] = rCurrentGuess[', j,
'] - rY[', i,
'] - ',
1615 'void', access=
'public')
1619 for entry
in self.
model.solver_info.jacobian.entry:
1620 used_vars.update(self.
_vars_in(entry.math))
1629 for entry
in self.
model.solver_info.jacobian.entry:
1630 var_i, var_j = entry.var_i, entry.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()
1654 'void', access=
'public')
1673 'void', access=
'public')
1679 linear_vars, update_eqns = [], {}
1681 for u, t, update_eqn
in SolverInfo(self.
model).get_linearised_odes():
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))
1692 if self.
config.dt_variable
in nodeset:
1700 linear_vars.sort(key=
lambda v: v.fullname())
1701 for i, u
in enumerate(linear_vars):
1719 if comma: self.
write(
',')
1721 self.
write(
'rY[', i,
']')
1722 self.
writeln(
'};', indent=
False)
1725 self.
writeln(CNS,
'* _p_solver = ', CNS,
'::Instance();')
1728 for j, i
in enumerate(idx_map):
1729 self.
writeln(
'rY[', i,
'] = _guess[', j,
'];')
1733 """Output the special methods needed for Rush-Larsen style cell models.
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
1740 rl_vars = self.doc._cml_rush_larsen
1745 'std::vector<double> &rDY',
1746 'std::vector<double> &rAlphaOrTau',
1747 'std::vector<double> &rBetaOrInf'],
1748 'void', access=
'public')
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))
1776 [
'const std::vector<double> &rDY',
1777 'const std::vector<double> &rAlphaOrTau',
1778 'const std::vector<double> &rBetaOrInf'],
1779 'void', access=
'public')
1781 self.
writeln(
'std::vector<double>& rY = rGetStateVariables();')
1785 conv = rl_vars[var][3]
or ''
1786 if conv: conv =
'*' + str(conv)
1787 if rl_vars[var][0] ==
'ab':
1792 self.
writeln(
'rY[', i,
'] = y_inf + (rY[', i,
'] - y_inf)*exp(-mDt', conv,
'*tau_inv);')
1796 self.
writeln(
'rY[', i,
'] = rBetaOrInf[', i,
'] + (rY[', i,
'] - rBetaOrInf[', i,
'])',
1797 '*exp(-mDt', conv,
'/rAlphaOrTau[', i,
']);')
1800 self.
writeln(
'rY[', i,
'] += mDt * rDY[', i,
'];')
1812 """This is used by self.output_grl?_mathematics to get equations for each variable separately.
1814 Returns a node set with the equations output.
1828 return var_nodeset | table_index_nodes_used
1831 """If we have analytic Jacobian information available from Maple, find the terms needed for GRL methods.
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.
1836 if not hasattr(self,
'jacobian_diagonal'):
1839 for entry
in self.
model.solver_info.jacobian.entry:
1840 if entry.var_i == entry.var_j:
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()
1849 """Compute the partial derivative of f(var) wrt var, the i'th variable in the state vector.
1851 This uses an analytic Jacobian if available; otherwise it approximates using finite differences.
1855 'std::vector<double>& rY',
'double delta',
'bool forceNumerical'],
1856 'double', access=
'public', defaults=[
'',
'',
'',
'false'])
1858 self.
writeln(
'double partialF;')
1861 self.
writeln(
'if (!forceNumerical && this->mUseAnalyticJacobian)')
1869 self.
writeln(
'partialF = ', nl=
False)
1876 self.
writeln(
'const double y_save = rY[', i,
'];')
1877 self.
writeln(
'rY[', i,
'] += delta;')
1879 self.
writeln(
'partialF = (temp-mEvalF[', i,
'])/delta;')
1880 self.
writeln(
'rY[', i,
'] = y_save;')
1883 self.
writeln(
'return partialF;')
1895 """Output the special methods needed for GRL1 style cell models.
1898 * UpdateTransmembranePotential update V_m
1899 * ComputeOneStepExceptVoltage does a GRL1 update for variables
except voltage
1900 * EvaluateYDerivativeI
for each variable I
1906 'void', access=
'public')
1908 self.
writeln(
'std::vector<double>& rY = rGetStateVariables();')
1909 self.
writeln(
'unsigned v_index = GetVoltageIndex();')
1910 self.
writeln(
'const double delta = 1e-8;')
1919 self.
writeln(
'if (fabs(partialF) < delta)')
1921 self.
writeln(
'rY[v_index] += evalF*mDt;')
1925 self.
writeln(
'rY[v_index] += (evalF/partialF)*(exp(partialF*mDt)-1.0);')
1932 'void', access=
'public')
1935 self.
writeln(
'std::vector<double>& rY = rGetStateVariables();')
1936 self.
writeln(
'const double delta = 1e-8;')
1955 self.
writeln(
'if (fabs(mPartialF[', i,
']) < delta)')
1961 self.
writeln(
'rY[', i,
'] += (', self.
code_namecode_name(var,
True),
'/mPartialF[', i,
'])*(exp(mPartialF[', i,
']*mDt)-1.0);')
1970 'std::vector<double>& rY'],
1971 'double', access=
'public')
1991 """Output the special methods needed for GRL2 style cell models.
1994 * Update TransmembranePotential update V_m
1995 * ComputeOneStepExceptVoltage does a GRL2 update for variables
except voltage
1996 * EvaluateYDerivativeI
for each variable I
2002 'void', access=
'public')
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];')
2017 self.
writeln(
'if (fabs(partialF) < delta)')
2019 self.
writeln(
'rY[v_index] += 0.5*evalF*mDt;')
2023 self.
writeln(
'rY[v_index] += (evalF/partialF)*(exp(partialF*0.5*mDt)-1.0);')
2027 self.
writeln(
'rY[v_index] = yinit;')
2031 self.
writeln(
'if (fabs(partialF) < delta)')
2033 self.
writeln(
'rY[v_index] = yinit + evalF*mDt;')
2037 self.
writeln(
'rY[v_index] = yinit + (evalF/partialF)*(exp(partialF*mDt)-1.0);')
2044 'void', access=
'public')
2047 self.
writeln(
'std::vector<double>& rY = rGetStateVariables();')
2048 self.
writeln(
'const double delta=1e-8;')
2049 self.
writeln(
'const unsigned size = GetNumberOfStateVariables();')
2051 self.
writeln(
'double y_save;')
2064 self.
writeln(
'for (unsigned var=0; var<size; var++)')
2067 self.
writeln(
'if (fabs(mPartialF[var]) < delta)')
2069 self.
writeln(
'rY[var] = mYInit[var] + 0.5*mDt*mEvalF[var];')
2073 self.
writeln(
'rY[var] = mYInit[var] + (mEvalF[var]/mPartialF[var])*(exp(mPartialF[var]*0.5*mDt)-1.0);')
2082 self.
writeln(
'y_save = rY[', i,
'];')
2083 self.
writeln(
'rY[', i,
'] = mYInit[', i,
'];')
2086 self.
writeln(
'rY[', i,
'] = y_save;')
2089 self.
writeln(
'for (unsigned var=0; var<size; var++)')
2092 self.
writeln(
'if (fabs(mPartialF[var]) < delta)')
2094 self.
writeln(
'rY[var] = mYInit[var] + mDt*mEvalF[var];')
2098 self.
writeln(
'rY[var] = mYInit[var] + (mEvalF[var]/mPartialF[var])*(exp(mPartialF[var]*mDt)-1.0);')
2108 'std::vector<double>& rY'],
2109 'double', access=
'public')
2122 """Output any named model attributes defined in metadata.
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)
2130 meta_id = model.cmeta_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,
';')
2148 """Output bottom boilerplate.
2150 End class definition, output ODE system information (to .cpp)
and
2151 serialization code (to .hpp),
and end the file.
2160 self.
writeln(
'SolverUtils::AddSummaryItem(s, "',
'Cell model' ,
'", "', self.
class_name ,
'");')
2165 '::v_SetInitialConditions()')
2173 def output_var(vector, var):
2175 self.
writeln(
'this->m', vector,
'Units.push_back("', var.units,
'");')
2179 init_val = getattr(var,
u'initial_value',
None)
2181 if init_val
is None:
2182 init_comm =
' // Value not given in model'
2188 self.
writeln(
'Vmath::Fill(m_nq, ', float(init_val),
',' ,
' '*(21-len(init_val)) ,
'm_cellSol[', i ,
'], 1);',
2193 if var.get_type() == VarTypes.Constant:
2194 output_var(
'Parameter', var)
2198 output_var(
'DerivedQuantity', var)
2202 self.
writeln(
'}',indent_level=1)
2257 self.
writeln(
'}', indent_level=0)
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()
2270 """Output a ci element, i.e. a variable lookup."""
2271 if hasattr(ci_elt,
'_cml_variable')
and ci_elt._cml_variable:
2276 prefix = [
'var_',
'd_dt_'][ode]
2277 varname = unicode(ci_elt)
2279 var = self.
varobj(varname)
2286 self.
write(prefix + varname)
2290 """Override base class method for special case of abs with 2 arguments.
2292 This comes from Maple
's Jacobians, and should generate signum of the second argument.
2295 if func_name ==
'fabs' and len(args) == 2:
2296 super(CellMLToNektarTranslator, self).
output_function(
'Signum', [args[1]], *posargs, **kwargs)
2298 super(CellMLToNektarTranslator, self).
output_function(func_name, args, *posargs, **kwargs)
2303 Return a list of units objects that give the possibilities for the dimensions
2304 of transmembrane ionic currents.
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]
2318 MEMBRANE_CAPACITANCE_NAME =
u'chaste_membrane_capacitance'
2321 INTERFACE_COMPONENT_NAME =
u'chaste_interface'
2325 """Add special units conversions for ionic currents.
2327 Adds conversions for the two other common conventions to/
from the units expected by Chaste,
2328 uA/cm^2. The cases are:
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.
2339 comp is a component to which we should add any necessary variables, i.e. Chaste
's capacitance.
2341 klass = CellMLToNektarTranslator
2342 model = converter.model
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
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))
2357 converter.add_special_conversion(microamps, chaste_units,
2358 lambda expr: converter.times_rhs_by(converter.divide_rhs_by(expr, model_Cm),
2360 converter.add_special_conversion(chaste_units, microamps,
2361 lambda expr: converter.divide_rhs_by(converter.times_rhs_by(expr, model_Cm),
2366 """Generate an interface component connecting the model to Chaste.
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.
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.
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.
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.
2383 config = doc._cml_config
2384 klass = CellMLToNektarTranslator
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]
2393 iface_comp = generator.get_interface_component()
2395 if config.options.convert_interfaces:
2396 warn_only =
not config.options.fully_automatic
and config.options.warn_on_units_errors
2398 logging.getLogger(
'units-converter').addHandler(notifier)
2400 klass.add_special_conversions(converter, iface_comp)
2401 generator.set_units_converter(converter)
2403 t = model.find_free_vars()[0]
2404 if not ms.dimensionally_equivalent(t.get_units()):
2406 raise TranslationError(
'Time does not have dimensions of time')
2407 generator.add_input(t, ms)
2408 if doc.model.get_option(
'backward_euler'):
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'):
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)
2421 if config.options.use_chaste_stimulus
and config.i_stim_var:
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)
2430 def add_oxmeta_ioput(oxmeta_name, units, inout):
2431 var = doc.model.get_variable_by_oxmeta_name(oxmeta_name, throw=
False)
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')
2440 if config.V_variable:
2441 config.V_variable = generator.add_input(config.V_variable, mV)
2442 ionic_vars = config.i_ionic_vars
2444 i_ionic = generator.add_output_function(
'i_ionic',
'plus', ionic_vars, current_units)
2445 config.i_ionic_vars = [i_ionic]
2447 if doc.model.get_option(
'use_data_clamp'):
2448 assert config.V_variable
and ionic_vars
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)
2454 config.i_data_clamp_conductance = generator.add_input(i_data_clamp_conductance, conductance_units)
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)'
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)
2469 def process_ci(elt):
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)
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]:
2485 for child
in getattr(elt,
'xml_children', []):
2486 process_ci_elts(child)
2487 process_ci_elts(dVdt)
2491 raise TranslationError(
"Creation of Chaste interface component failed:\n " + str(errors))
2492 generator.finalize(errh, check_units=
False)
2494 if config.options.convert_interfaces:
2495 converter.add_conversions_for_component(iface_comp)
2496 converter.finalize(errh, check_units=
False)
2498 logging.getLogger(
'units-converter').removeHandler(notifier)
2499 if notifier.messages:
2500 msg =
'Problems occurred converting model variables to Chaste units.\n'
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)
2507 print >>sys.stderr, msg
def output_rush_larsen_mathematics(self)
def output_model_attributes(self)
def output_derived_quantities(self)
def output_nonlinear_state_assignments(self, nodeset=None)
def add_special_conversions(converter, comp)
def output_intracellular_calcium(self)
def output_constructor(self, params, base_class_params)
def modifier_call(self, var, current_value)
def output_get_i_ionic(self)
def unsigned_v_index(self)
def output_evaluate_y_derivatives(self, method_name='EvaluateYDerivatives')
def final_configuration_hook(self)
def output_grl2_mathematics(self)
def translate(self, *args, **kwargs)
def get_stimulus_assignment(self)
def output_mathematics(self)
def output_lut_class(self)
def output_lut_indexing_methods(self)
def output_extra_constructor_content(self)
def vector_initialise(self, vector, size)
def output_grl_compute_partial(self, i, var)
def output_equations(self, nodeset, zero_stimulus=False)
def output_derivative_calculations_grl(self, var, assign_rY=False, extra_nodes=set(), extra_table_nodes=set())
def output_includes(self, base_class=None)
def vector_index(self, vector, i)
def output_table_index_checking(self, key, idx, call_method=True)
def get_current_units_options(model)
def output_verify_state_variables(self)
def output_variable(self, ci_elt, ode=False)
def output_function(self, func_name, args, *posargs, **kwargs)
def writeln_hpp(self, *args, **kwargs)
def output_chaste_lut_methods(self)
def generate_interface(doc, solver_info)
def output_state_assignments(self, exclude_nonlinear=False, assign_rY=True, nodeset=None, pointer='')
def output_bottom_boilerplate(self)
def set_access(self, access)
def code_name(self, var, *args, **kwargs)
def output_table_index_generation_code(self, key, idx, call_method=True)
def lut_parameters(self, key)
def output_method_start(self, method_name, args, ret_type, access=None, defaults=[])
def vector_create(self, vector, size)
def output_top_boilerplate(self)
def output_assignment(self, expr)
def output_derivative_calculations(self, state_vars, assign_rY=False, extra_nodes=set(), extra_table_nodes=set())
def output_backward_euler_mathematics(self)
def output_grl1_mathematics(self)
def output_serialize_method(self)
def output_cell_parameters(self)
def calculate_lookup_table_indices(self, nodeset, time_name=None)
def output_default_stimulus(self)
def output_lhs(self, expr)
def find_grl_partial_derivatives(self)
def output_expr(self, expr, paren)
def output_variable(self, ci_elt, ode=False)
def get_captured_output(self)
def output_equations(self, nodeset)
def set_indent(self, level=None, offset=None)
def output_comment(self, *args, **kwargs)
def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[])
Dependency related methods #.
def code_name(self, var, ode=False, prefix=None)
def output_table_index_checking(self, key, idx)
def output_doxygen(self, *args, **kwargs)
def writeln(self, *args, **kwargs)
def output_lut_methods(self)
def output_table_index_generation_code(self, key, idx)
def output_assignment(self, expr)
def open_block(self, **kwargs)
def lut_factor(self, idx, include_comma=False, include_type=False)
def output_lut_row_lookup_memory(self)
def output_lut_generation(self, only_index=None)
def varobj(self, varname)
def close_block(self, blank_line=True, **kwargs)
def output_lut_deletion(self, only_index=None)
def var_display_name(self, var)
string STMT_END
Various language tokens #.
def output_table_index_generation(self, time_name, nodeset=set())
def output_lut_declarations(self)
def simplify(self, other_units=None, other_exponent=1)
def dimensionally_equivalent(self, other_units)
def version_comment(note_time=True)
def DEBUG(facility, *args)
InputIterator find(InputIterator first, InputIterator last, InputIterator startingpoint, const EqualityComparable &value)