2from __future__
import division
4"""Copyright (c) 2005-2016, University of Oxford.
7University of Oxford means the Chancellor, Masters and Scholars of the
8University of Oxford, having an administrative office at Wellington
9Square, Oxford OX1 2JD, UK.
11This file is part of Chaste.
13Redistribution and use in source and binary forms, with or without
14modification, are permitted provided that the following conditions are met:
15 * Redistributions of source code must retain the above copyright notice,
16 this list of conditions and the following disclaimer.
17 * Redistributions
in binary form must reproduce the above copyright notice,
18 this list of conditions
and the following disclaimer
in the documentation
19 and/
or other materials provided
with the distribution.
20 * Neither the name of the University of Oxford nor the names of its
21 contributors may be used to endorse
or promote products derived
from this
22 software without specific prior written permission.
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS"
25AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
28LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
30GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37This part of PyCml deals
with converting CellML models into
38programming language code, primarily C++ compatible
with Chaste, but
39supporting a few other languages also (
and easily extensible).
42import optparse #adds options to the command line parsing stuff
43import os #imports OS functions
44import re #imports regular expression operations
45import time #lets you do various time-related operations
46import sys #imports system functions (e.g. which path we are on)
47from cStringIO import StringIO #"This module implements a file-like class, StringIO, that reads and writes a string buffer (also known as memory files)."
49# Common CellML processing stuff, all of these are in the /pycml folder
58__version__ =
"$Revision: 25950 $"[11:-2]
63 """Generate a version comment, with optional time info."""
65 t =
'\non ' + time.asctime()
68 text =
"""Processed by pycml - CellML Tools in Python
69 (translators: %s, pycml: %s, optimize: %s)%s""" % (
70 __version__, pycml.__version__, optimize.__version__, t)
77 if isinstance(e, cellml_variable):
79 elif isinstance(e, mathml_apply):
80 v = e.assigned_variable()
82 r = (v==e, v.name, v.get_usage_count())
89 """Error thrown if CellML translation fails."""
92class ConfigurationError(ValueError):
93 """Error thrown if configuration file is invalid."""
100 Base class for
translators from CellML to programming languages.
102 Provides various methods & attributes that can be overridden to
103 achieve the desired output language
and style.
105 Also contains a registration system
for subclasses, so the
106 command-line client can know what translators are available. See
107 the register method
for more information.
118 """Register a new translator subclass.
120 Registers the subclass `subclass' with name `name' in the
122 given
is already
in use, raises NameAlreadyRegistered.
132 """Generate an interface component connecting the model to whatever will use it.
134 Stub method that subclasses can override to implement this functionality.
143 COMMENT_START =
'// '
144 DOXYGEN_COMMENT_START =
'//! '
147 TYPE_DOUBLE =
'NekDouble '
149 TYPE_CONST_DOUBLE =
'const NekDouble '
150 TYPE_CONST_UNSIGNED =
'const unsigned '
161 USES_SUBSIDIARY_FILE =
False
163 FILE_EXTENSIONS = {
'cpp':
'h',
167 def __init__(self, add_timestamp=True, options=None):
168 """Create a translator."""
184 """Raise a translation error.
186 lines is a list of strings describing what went wrong.
187 A TranslationError
with that message will be raised.
189 If xml
is given, it should be an element, which will be
190 pretty-printed
and included
in the error.
193 lines.extend(xml.xml(indent =
u'yes',
194 omitXmlDeclaration =
u'yes').split(
'\n'))
199 """Get the current document's configuration store."""
200 return getattr(self.
doc,
'_cml_config',
None)
202 def translate(self, doc, model_filename, output_filename=None,
203 subsidiary_file_name=None,
204 class_name=None, v_variable=None,
206 lookup_method_prefix='', row_lookup_method=False,
207 lt_index_uses_floor=True, constrain_table_indices=False):
208 """Generate code for the given model.
210 doc should be an instance of cellml_model representing a
211 valid CellML model, such as might be produced
from a call
214 ... model_filename,
True)
216 model_filename
is the filename of the input model.
217 The output program will by default be generated
in the same
218 folder, but
with a different extension. This can be
219 overridden by supplying the output_filename keyword argument.
221 By default the name of the
class representing the
model will
222 be derived
from the model name. This can be overridden by
223 passing an alternative
as the class_name argument.
225 The variable representing the transmembrane potential should
226 be passed
in using the v_variable argument.
228 By default this method will perform some setup
and then call
232 To alter this,
pass a callable
as the continuation parameter;
233 this will then be called instead.
235 lookup_method_prefix
and row_lookup_method can be used to
236 customise some aspects of lookup table usage. The former
is
237 used by the Chaste translator to place lookup tables within a
238 singleton
class,
while the latter can improve cache
239 performance by looking up all tables
in a single call,
and
240 returning an array of the results.
242 lt_index_uses_floor specifies whether to use the floor()
243 function to calculate the index into the lookup tables,
or
244 just cast to unsigned.
246 constrain_table_indices specifies whether to throw an
247 exception
if lookup table index variables go outside the
248 bounds specified (default),
or just to cap them at the bounds.
253 if class_name
is None:
261 self.
error([
"Model has more than one free variable; exiting.",
264 if self.
model.get_option(
'protocol'):
266 for var
in self.
model.get_all_variables():
267 if var.get_type() == VarTypes.Unknown:
270 self.
error([
"Model has no free variable; exiting."])
280 if var
is v_variable:
291 if not doc.lookup_tables:
299 if output_filename
is None:
321 """A hook for subclasses to do some final configuration."""
325 """Generate a name for our output file, based on the input file."""
326 return os.path.splitext(model_filename)[0] +
'.cpp'
329 """Generate a name for the subsidiary output file, based on the main one.
331 Returns a pair (main_output_file_name, subsidiary_file_name). This is in
332 case the user specifies (e.g.) a .hpp file
as the main output - we consider
333 the main output to be the .cpp file.
335 base, ext = os.path.splitext(output_filename)
349 return output_filename,
None
350 subsidiary_filename = base +
'.' + new_ext
352 output_filename, subsidiary_filename = subsidiary_filename, output_filename
353 return output_filename, subsidiary_filename
356 """Set subsequent main-file writes to go to the subsidiary file instead.
358 Supplying a False argument reverts this setting.
360 Has no effect
if not using a subsidiary file.
365 """Write a line to our output file.
367 Takes any number of strings as input,
and writes them out to file.
369 Unless the keyword argument indent
is given
as False, then the
370 output will be indented to the level set by self.
set_indent().
371 Setting indent_level will override this value.
372 Setting indent_offset will adjust the current level temporarily.
374 If nl
is set to
False then a newline character will
not be
375 appended to the output.
377 If subsidiary=
True, then the line will be written to the subsidiary
378 output file instead of the main one. An error will be raised
if
379 there
is no subsidiary output file.
383 self.
error(
'Tried to write to non-existent subsidiary file')
388 indent = kwargs.get(
'indent',
True)
389 nl = kwargs.get(
'nl',
True)
392 level += kwargs.get(
'indent_offset', 0)
394 target.write(
''.join(map(str, args)))
399 """Write to our output file.
401 This variant does not indent the output,
or add a newline.
403 self.writeln(indent=False, nl=
False, *args)
406 """Make subsequent output operations write to a string buffer."""
408 self.
out = StringIO()
411 """Stop capturing output, and return what was captured as a string."""
412 output = self.
out.getvalue()
417 """Output a (multi-line) string as a comment."""
419 if kwargs.get(
'pad',
False):
421 comment =
''.join(map(str, args))
422 lines = comment.split(
'\n')
424 self.
writeln(start, line, **kwargs)
427 """Output a (multi-line) string as a Doxygen comment."""
432 """Set the indentation level for subsequent writes.
434 If offset is given, adjust the level by that amount, otherwise
435 set it to an absolute value.
437 if offset
is not None:
444 Return the full name of var in a form suitable
for inclusion
in a
447 If ode
is True then
return the name of the derivative of var
448 instead. We go directly to the source variable
in this case,
449 rather than including intermediate assignment statements
as is
450 done
for connections.
453 prefix = [
'var_',
'd_dt_'][ode]
455 var = var.get_source_variable(recurse=
True)
456 name = prefix + var.fullname(cellml=
True)
460 """Return the variable object that has code_name varname."""
461 return cellml_variable.get_variable_object(self.
model, varname)
464 """Return a display name for the given variable.
466 If it has an oxmeta name, uses that. Otherwise, looks first for a bqbiol:
is annotation,
467 or uses the cmeta:id
if present,
or the name attribute
if not. If there
is an interface
468 component, strip the name of it out of the display name.
471 name = var.oxmeta_name
473 for uri
in var.get_rdf_annotations((
'bqbiol:is', NSS[
'bqbiol'])):
475 name = uri[1 + uri.rfind(
'#'):]
478 if hasattr(var,
u'id')
and var.id:
482 iface = getattr(self.
model,
'interface_component_name',
'#N/A#')
483 if name.startswith(iface):
484 name = name[len(iface)+2:]
490 Get the include guard (for C/C++ output)
for this cell model,
491 based on the
class name.
496 """Output top boilerplate."""
502 self.
writeln(
'#include <cmath>')
503 self.
writeln(
'#include "AbstractOdeSystem.hpp"')
504 self.
writeln(
'#include "Exception.hpp"')
505 self.
writeln(
'#include "AbstractStimulusFunction.hpp"\n')
509 self.
writeln(
'AbstractStimulusFunction *mpStimulus;\n',
513 self.
writeln(
'const static unsigned _NB_OF_STATE_VARIABLES_ = ',
517 self.
writeln(
'//', (
'-'*66),
'\n')
520 '(AbstractStimulusFunction *stim)')
521 self.
writeln(
' : AbstractOdeSystem(_NB_OF_STATE_VARIABLES_, ',
524 self.
writeln(
'mpStimulus = stim;\n')
526 self.
writeln(
'mVariableNames.push_back("', var.name,
'");')
527 self.
writeln(
'mVariableUnits.push_back("', var.units,
'");')
528 init_val = getattr(var,
u'initial_value',
None)
530 init_comm =
' // Value not given in model'
535 self.
writeln(
'mInitialConditions.push_back(', init_val,
');',
549 self.
writeln(
'void EvaluateYDerivatives (')
551 self.
writeln(
' const std::vector<double> &rY,')
552 self.
writeln(
' std::vector<double> &rDY)')
558 ' = rY[', str(i),
'];')
559 self.
writeln(
'// Units: ', var.units,
'; Initial value: ',
560 getattr(var,
u'initial_value',
'Unknown'))
567 """Output the mathematics in this model."""
569 for expr
in self.
model.get_assignments():
572 if isinstance(expr, mathml_apply)
and expr.is_assignment():
573 var = expr.assigned_variable()
574 elif isinstance(expr, cellml_variable):
576 if not (var
and var.get_usage_count() == 0):
581 """Output bottom boilerplate"""
593 """Output an assignment expression."""
594 if isinstance(expr, cellml_variable):
598 if t == VarTypes.Mapped:
602 self.
code_name(expr.get_source_variable()),
605 elif t == VarTypes.Constant:
611 if 'stim_amplitude' in str(self.
code_name(expr)):
626 opers = expr.operands()
633 indent=
False, pad=
True)
636 """Output the left hand side of an assignment expression."""
637 if expr.localName ==
'ci':
639 elif expr.operator().localName ==
'diff':
640 self.
write(self.
code_name(expr.operator().dependent_variable, ode=
True))
643 """Output a ci element, i.e. a variable lookup."""
647 """Output the expression expr.
649 If paren is True then the context has requested parentheses around the
650 output;
if expr requires them then they will be added.
654 elif isinstance(expr, mathml_apply):
656 elif isinstance(expr, mathml_piecewise):
658 elif isinstance(expr, mathml_ci):
660 elif expr.localName ==
u'cn':
662 elif expr.localName ==
u'degree':
665 elif expr.localName ==
u'logbase':
668 elif expr.localName ==
u'true':
670 elif expr.localName ==
u'false':
672 elif expr.localName ==
u'pi':
674 elif expr.localName ==
u'exponentiale':
677 self.
error([
"Unsupported expression element " + expr.localName],
681 """Output the plain number expr.
683 We make all constants parse as doubles to avoid problems
with
684 integer division
or numbers too large
for the int type.
686 Negative numbers will be prefixed by a space to avoid unwanted
687 decrement operations.
693 if not '.' in num
and not 'e' in num:
698 """Evaluate a number.
700 If a (unicode) string, convert to float.
701 If a cn element, call its evaluate method.
703 if isinstance(expr, mathml_cn):
704 return expr.evaluate()
706 return float(unicode(expr))
710 function_map = {
'power':
'pow',
'abs':
'fabs',
'ln':
'log',
'exp':
'exp',
711 'floor':
'floor',
'ceiling':
'ceil',
712 'factorial':
'factorial',
713 'not':
'!',
'rem':
'fmod',
714 'sin':
'sin',
'cos':
'cos',
'tan':
'tan',
715 'sec':
'1/cos',
'csc':
'1/sin',
'cot':
'1/tan',
716 'sinh':
'sinh',
'cosh':
'cosh',
'tanh':
'tanh',
717 'sech':
'1/cosh',
'csch':
'1/sinh',
'coth':
'1/tanh',
718 'arcsin':
'asin',
'arccos':
'acos',
'arctan':
'atan',
719 'arcsinh':
'asinh',
'arccosh':
'acosh',
'arctanh':
'atanh'}
722 recip_trig = {
'arcsec':
'arccos',
'arccsc':
'arcsin',
'arccot':
'arctan',
723 'arcsech':
'arccosh',
'arccsch':
'arcsinh',
'arccoth':
'arctanh'}
725 nary_ops = {
'plus':
'+',
'times':
'*',
726 'and':
'&&',
'or':
'||'}
727 binary_ops = {
'divide':
'/',
728 'xor':
'!=',
'eq':
'==',
'neq':
'!=',
729 'geq':
'>=',
'leq':
'<=',
'gt':
'>',
'lt':
'<'}
732 """Output an <apply> expression.
734 paren is True if the context has requested parentheses.
739 expr.operands(), paren)
742 expr.operands(), paren, reciprocal=
True)
743 elif op.localName ==
u'root':
745 elif op.localName ==
u'log':
749 expr.operands(), paren)
752 expr.operands(), paren, expr)
753 elif op.localName ==
u'minus':
755 elif op.localName ==
u'diff':
760 self.
error([
"Unsupported operator element " + str(op.localName)], xml=expr)
763 """Output a function call with name func_name and arguments args.
765 Parentheses are not required so paren
is ignored.
766 If reciprocal
is True then
pass the reciprocal of each arg to
769 self.write(func_name + '(')
772 if comma: self.
write(
', ')
782 """Output an n-ary operator (using infix notation).
784 If paren is True, enclose the output
in parentheses.
789 for operand
in operands:
790 if op: self.
write(
' ' + operator +
' ')
796 """Output a unary operator (using prefix notation)."""
803 """Output a binary operator.
805 As output_nary_operator, but checks that len(list(operands)) == 2.
807 operands = list(operands)
808 if len(operands) != 2:
809 self.
error([
"Binary operator" + operator +
810 "does not have 2 operands."], xml=expr)
813 special_roots = {2:
'sqrt', 3:
'cbrt'}
815 """Output a root taken to some degree.
817 If a degree qualifier element is not provided, uses default 2.
819 if hasattr(expr,
u'degree'):
834 """Output a logarithm to the given base, which defaults to base 10."""
835 if hasattr(expr,
u'logbase'):
849 """Output either a unary or binary minus.
851 Which is chosen depends on the number of operands.
853 operands = list(expr.operands())
854 if len(operands) == 1:
860 """Output the piecewise expression expr.
862 We use a cascading ternary if expression
for simplicity.
865 for piece
in getattr(expr,
u'piece', []):
870 if hasattr(expr,
u'otherwise'):
877 if paren: self.
write(
'(')
879 if paren: self.
write(
')')
882 """Open a new code block and increase indent."""
886 """Close a code block and decrease indent."""
900 """Method moved to cellml_model."""
914 """Output the mathematics described by nodeset.
916 nodeset represents a subset of the assignments in the model.
917 Output assignments
in the order given by a topological sort,
918 but only include those
in nodeset.
920 Since a set of assignments
is given, this method does
not
921 check whether variables are used - it
is assumed that only
922 assignments that are actually wanted are given
in nodeset.
924 for expr
in (e
for e
in self.
model.get_assignments()
if e
in nodeset):
929 """Return a set of variable objects used in the given expression.
931 Will include state variables. If the expression includes a derivative, the defining equation
932 for that derivative will be included
in the set. Also
if an expression
is being
933 replaced by a lookup table, this will only include the table key variable.
937 key_var = self.
varobj(expr.getAttributeNS(NSS[
'lut'],
u'var'))
938 key_var = key_var.get_source_variable(recurse=
True)
940 elif isinstance(expr, mathml_ci):
941 varobj = getattr(expr,
'_cml_variable',
None)
943 varname = unicode(expr)
944 varobj = self.
varobj(varname.strip())
947 elif isinstance(expr, mathml_apply)
and expr.operator().localName ==
u'diff':
948 dep_varname = unicode(expr.ci)
949 varobj = self.
varobj(dep_varname.strip())
950 res.add(varobj.get_ode_dependency(self.
free_vars[0]))
951 elif hasattr(expr,
'xml_children'):
952 for child
in expr.xml_children:
981 """Search for lookup tables used in this document.
983 Store a list of suitable expressions on the document root.
984 Generate a dictionary mapping tables to their index variables.
988 doc.lookup_tables = doc.xml_xpath(
u"//*[@lut:possible='yes']")
989 doc.lookup_tables.sort(cmp=element_path_cmp)
991 doc.lookup_table_indexes = {}
993 doc.lookup_tables_num_per_index = {}
994 if not doc.lookup_tables:
998 table_indexes = [int(getattr(expr,
u'table_index', -1))
999 for expr
in doc.lookup_tables]
1000 tidx = max(table_indexes) + 1
1003 for expr
in doc.lookup_tables:
1004 if hasattr(expr,
u'table_name'):
1005 idx = expr.table_index
1006 table_numbers[idx] = max(table_numbers.get(idx, 0), 1 + int(expr.table_name))
1008 for expr
in doc.lookup_tables:
1010 comp = expr.get_component()
1011 var = comp.get_variable_by_name(expr.var)
1012 var = var.get_source_variable(recurse=
True)
1013 key = (expr.min, expr.max, expr.step, var)
1014 if not key
in doc.lookup_table_indexes:
1015 var._cml_modifiable =
True
1016 if hasattr(expr,
u'table_index'):
1017 doc.lookup_table_indexes[key] = expr.table_index
1019 doc.lookup_table_indexes[key] = unicode(tidx)
1021 expr.xml_set_attribute((
u'lut:table_index', NSS[
'lut']),
1022 doc.lookup_table_indexes[key])
1024 if not hasattr(expr,
u'table_name'):
1025 tnum = table_numbers.get(doc.lookup_table_indexes[key], 0)
1026 expr.xml_set_attribute((
u'lut:table_name', NSS[
'lut']), unicode(tnum))
1027 table_numbers[doc.lookup_table_indexes[key]] = tnum + 1
1029 table_index_map = {}
1032 for key
in sorted(doc.lookup_table_indexes.keys()):
1034 table_index_map[doc.lookup_table_indexes[key]] = idx
1035 table_name_map[idx] = {}
1036 doc.lookup_table_indexes[key] = idx
1037 doc.lookup_tables_num_per_index[idx] = 0
1042 candidates = doc.lookup_tables[:]
1043 doc.lookup_tables = []
1045 for expr
in candidates:
1046 tid = (expr.table_index, expr.table_name)
1047 if not tid
in listed:
1049 doc.lookup_tables.append(expr)
1051 expr.table_index = table_index_map[expr.table_index]
1052 table_name_map[expr.table_index][expr.table_name] = unicode(doc.lookup_tables_num_per_index[expr.table_index])
1053 expr.table_name = table_name_map[expr.table_index][expr.table_name]
1055 doc.lookup_tables_num_per_index[expr.table_index] += 1
1058 expr.table_index = table_index_map[expr.table_index]
1059 expr.table_name = table_name_map[expr.table_index][expr.table_name]
1063 """Get the code for accessing the i'th element of the given table.
1065 return '_lookup_table_%s[%s][%s]' % (table_index, i, table_name)
1068 """Get the bounds and step size for a particular table.
1070 key should be a key into self.lookup_table_indices.
1071 Returns (min, max, step, step_inverse) suitable for putting
in generated code.
1073 return key[0:3] + [unicode(1 / float(key[2]))]
1076 """Return the equivalent of '1 + (unsigned)((max-min)/step+0.5)'."""
1077 return '1 + (unsigned)((%s-%s)/%s+0.5)' % (max, min, step)
1080 """Output code to generate lookup tables.
1082 There should be a list of suitable expressions available as self.
doc.lookup_tables,
1083 to save having to search the whole model.
1085 If only_index
is given, only generate tables using the given table index key.
1090 for key, idx
in self.
doc.lookup_table_indexes.iteritems():
1091 if only_index
is None or only_index == idx:
1095 self.
writeln(
'_lookup_table_', idx, self.
EQ_ASSIGN,
'new double[_table_size_', idx,
1096 '][', self.
doc.lookup_tables_num_per_index[idx],
']', self.
STMT_END)
1098 for expr
in self.
doc.lookup_tables:
1099 var = expr.component.get_variable_by_name(expr.var)
1100 key = (expr.min, expr.max, expr.step, var.get_source_variable(recurse=
True))
1101 idx = self.
doc.lookup_table_indexes[key]
1102 if only_index
is not None and only_index != idx:
1106 self.
writeln(
'for (unsigned i=0 ; i<_table_size_', idx,
'; i++)')
1117 """Output code to delete memory allocated for lookup tables."""
1118 for idx
in self.
doc.lookup_table_indexes.itervalues():
1119 if only_index
is None or only_index == idx:
1120 self.
writeln(
'if (_lookup_table_', idx,
')')
1127 """Output declarations for the lookup tables."""
1130 for idx
in self.
doc.lookup_table_indexes.itervalues():
1131 num_tables = unicode(self.
doc.lookup_tables_num_per_index[idx])
1136 """Output declarations the variables used to index this table."""
1138 factor = self.
lut_factor(idx, include_type=
True)
1145 """Output declarations for the lookup table indices."""
1147 for idx
in self.
doc.lookup_table_indexes.itervalues():
1152 """Output the methods which look up values from lookup tables."""
1156 self.
output_comment(
'Methods to look up values from lookup tables')
1158 for expr
in self.
doc.lookup_tables:
1160 idx = expr.table_index
1161 self.
writeln(
'inline double _lookup_', j,
'(unsigned i',
1162 self.
lut_factor(
'', include_type=
True, include_comma=
True),
')')
1170 """Write the lookup calculation for a single entry.
1172 Used by output_lut_row_lookup_methods and output_lut_methods.
1176 if self.
config.options.lookup_type ==
'linear-interpolation':
1184 """Write methods that return a whole row of a lookup table.
1186 Note: assumes that table names are numbered sequentially from 0.
1190 for key, idx
in self.
doc.lookup_table_indexes.iteritems():
1191 num_tables = unicode(self.
doc.lookup_tables_num_per_index[idx])
1192 self.
writeln(
'double* _lookup_', idx,
'_row(unsigned i',
1193 self.
lut_factor(
'', include_type=
True, include_comma=
True),
')')
1195 self.
writeln(
'for (unsigned j=0; j<', num_tables,
'; j++)')
1199 self.
writeln(
'return _lookup_table_', idx,
'_row;')
1205 """Output declarations for the memory used by the row lookup methods."""
1207 for key, idx
in self.
doc.lookup_table_indexes.iteritems():
1208 min, max, step, var = key
1209 num_tables = unicode(self.
doc.lookup_tables_num_per_index[idx])
1210 self.
writeln(
'double _lookup_table_', idx,
'_row[', num_tables,
'];')
1215 """Return True iff expr can be replaced by a lookup table.
1217 Uses annotations from a previous analysis.
"""
1218 return expr.getAttributeNS(NSS[
'lut'],
u'possible',
'') ==
u'yes'
1221 """Return all lookup tables used directly in computing this node.
1223 If this is an expression node, checks all its children
for table
1224 lookups,
and returns the set of table indices used.
1227 if isinstance(node, amara.bindery.element_base):
1229 result.add(node.table_index)
1231 for child
in node.xml_children:
1235 def lut_factor(self, idx, include_comma=False, include_type=False):
1236 """Return code for any extra factor needed to do a table lookup.
1238 Will return the empty string unless linear interpolation
is being used.
1240 if self.
config.options.lookup_type ==
'linear-interpolation':
1241 factor =
'_factor_' + str(idx)
1242 if include_type: factor = self.
TYPE_DOUBLE + factor
1243 if include_comma: factor =
', ' + factor
1249 """Output code to look up expr in the appropriate table."""
1250 i = expr.table_index
1252 self.
write(
'_lt_', i,
'_row[', expr.table_name,
']')
1255 '(_table_index_', i, self.
lut_factor(i, include_comma=
True),
')')
1259 """Output code to calculate indexes into any lookup tables.
1261 If time_name is given
and table bounds are being checked, the time value will be included
in the
1262 error message. Note that we need to
pass it
in, since
in some contexts the free variable
is not
1265 If nodeset
is given, then filter the table indices calculated so
1266 that only those needed to compute the nodes
in nodeset are defined.
1268 A nodeset
is required
if any table indices are computed variables rather than state variables.
1269 In this case, we use the equations within nodeset to calculate the values of the indices,
and
1270 return a set containing just those nodes used, so we can avoid recalculating them later.
1272 tables_to_index = set()
1274 for node
in nodeset:
1276 if tables_to_index
or not nodeset:
1278 for key, idx
in self.
doc.lookup_table_indexes.iteritems():
1279 if not nodeset
or idx
in tables_to_index:
1281 if var.get_type()
is VarTypes.Computed:
1283 raise TranslationError(
'Unable to generate lookup table indexed on', var,
'as it is a computed variable')
1286 nodes_used.update(var_nodes)
1288 if self.
config.options.check_lt_bounds:
1289 self.
writeln(
'#define COVERAGE_IGNORE', indent=
False)
1290 self.
writeln(
'if (_oob_', idx,
')')
1291 if time_name
is None:
1292 dump_state_args =
'rY'
1294 dump_state_args =
'rY, ' + time_name
1296 ' outside lookup table range", ', dump_state_args,
'));', indent_offset=1)
1297 self.
writeln(
'#undef COVERAGE_IGNORE', indent=
False)
1303 """Check whether a table index is out of bounds."""
1304 if self.
config.options.check_lt_bounds:
1309 self.
writeln(
'if (', varname,
'>', max,
' || ', varname,
'<', min,
')')
1311 self.
writeln(
'#define COVERAGE_IGNORE', indent=
False)
1317 self.
writeln(
'#undef COVERAGE_IGNORE', indent=
False)
1321 """Method called by output_table_index_generation to output the code for a single table."""
1322 index_type =
'const unsigned '
1323 factor_type =
'const double '
1324 row_type =
'const double* const '
1327 offset =
'_offset_' + idx
1328 offset_over_step = offset +
'_over_table_step'
1332 offset,
' * ', step_inverse, self.
STMT_END)
1333 idx_var =
'_table_index_' + str(idx)
1334 if self.
config.options.lookup_type ==
'nearest-neighbour':
1336 self.
writeln(index_type, idx_var,
' = (unsigned) round(', offset_over_step,
');')
1338 self.
writeln(index_type, idx_var,
' = (unsigned) (', offset_over_step,
'+0.5);')
1341 self.
writeln(index_type, idx_var,
' = (unsigned) floor(', offset_over_step,
');')
1343 self.
writeln(index_type, idx_var,
' = (unsigned)(', offset_over_step,
');')
1346 self.
writeln(factor_type, factor,
' = ', offset_over_step,
' - ', idx_var, self.
STMT_END)
1349 '_row(', idx_var, self.
lut_factor(idx, include_comma=
True),
');')
1360 """Add information for specialised translator classes into a model."""
1362 """Add information for the solvers as XML.
1364 The Jacobian and linearity analyses store their results
in
1365 Python data structures
as attributes of this object.
1366 Transcribe these into XML
in a child <solver_info> element.
1368 If any of these elements exist
in the model they will be left
1369 unaltered, unless force
is set to
True.
1371 This constructor just sets up the container element; call one
1372 of the add_* methods to actually add the information to it.
1375 if force
and hasattr(model,
u'solver_info'):
1376 model.xml_remove_child(model.solver_info)
1377 if hasattr(model,
u'solver_info'):
1378 solver_info = model.solver_info
1380 solver_info = model.xml_create_element(
u'solver_info', NSS[
u'solver'])
1381 model.xml_append(solver_info)
1387 """Actually add the info."""
1395 """Add a reference to the variable representing dt."""
1398 if not hasattr(solver_info,
u'dt'):
1400 elt = model.xml_create_element(
u'dt', NSS[
u'solver'], content=dt.fullname(cellml=
True))
1401 solver_info.xml_append(elt)
1402 self.
_model._add_sorted_assignment(dt)
1405 """The name of the transmembrane potential."""
1408 if not hasattr(solver_info,
u'transmembrane_potential'):
1409 v_elt = model.xml_create_element(
1410 u'transmembrane_potential', NSS[
u'solver'],
1411 content=model._cml_transmembrane_potential.fullname())
1412 solver_info.xml_append(v_elt)
1415 """Linearised ODEs - where du/dt = g + hu (and g, h are not functions of u).
1417 Structure looks like:
1422 <bvar><ci>t</ci></bvar>
1441 if not hasattr(solver_info,
u'linear_odes')
and model._cml_linear_update_exprs:
1442 odes_elt = model.xml_create_element(
u'linear_odes', NSS[
u'solver'])
1443 solver_info.xml_append(odes_elt)
1444 odes_math = model.xml_create_element(
u'math', NSS[
u'm'])
1445 odes_elt.xml_append(odes_math)
1446 linear_vars = model._cml_linear_update_exprs.keys()
1447 linear_vars.sort(key=
lambda v: v.fullname())
1448 free_var = model._cml_free_var
1449 for var
in linear_vars:
1450 g, h = model._cml_linear_update_exprs[var]
1451 hu = mathml_apply.create_new(model,
u'times', [h, var.fullname()])
1452 rhs = mathml_apply.create_new(model,
u'plus', [g, hu])
1453 odes_math.xml_append(mathml_diff.create_new(
1454 model, free_var.fullname(), var.fullname(), rhs))
1460 If PE will be performed on a model with a single component, then we
'll need full names in
1461 the variable attributes.
1463 if vname[:4] ==
'var_' and len(self.
_model.component) == 1
and not self.
_model.component.ignore_component_name:
1464 name = unicode(
'var_' + self.
_model.component.name +
'__' + vname[4:])
1466 name = unicode(vname)
1470 """Jacobian matrix elements.
1472 Structure looks like:
1474 [<math> assignments of common sub-terms </math>]
1475 <entry var_i='varname' var_j=
'varname'>
1476 <math> apply|cn|ci ...</math>
1482 if model._cml_jacobian
and model._cml_jacobian_full:
1483 jac = model._cml_jacobian[1]
1486 jac = model._cml_jacobian
1487 if not hasattr(solver_info,
u'jacobian')
and jac:
1488 jac_elt = model.xml_create_element(
u'jacobian', NSS[
u'solver'])
1489 solver_info.xml_append(jac_elt)
1491 if model._cml_jacobian_full:
1493 temporaries = model._cml_jacobian[0]
1497 jac_vars = jac.keys()
1499 for v_i, v_j
in jac_vars:
1503 entry = model.xml_create_element(
u'entry', NSS[
u'solver'], attributes=attrs)
1504 jac_elt.xml_append(entry)
1506 entry.xml_append(entry_doc.math)
1513 PE has just been performed, so we need to update variable names occurring outside
1514 the modifiable mathematics sections.
1516 jac_elt = getattr(self._solver_info, u'jacobian',
None)
1517 for entry
in getattr(jac_elt,
u'entry', []):
1518 for vlabel
in [
'var_i',
'var_j']:
1519 vname = getattr(entry, vlabel)
1521 new_name = var.get_source_variable(recurse=
True).fullname()
1522 setattr(entry, vlabel, new_name)
1526 new_name = var.get_source_variable(recurse=
True).fullname()
1527 dt_elt.xml_remove_child(unicode(dt_elt))
1528 dt_elt.xml_append(unicode(new_name))
1531 """Add ionic current information as XML for solvers to use."""
1535 if hasattr(model.xml_parent,
'_cml_config')
and not hasattr(solver_info,
u'ionic_current'):
1536 conf = model.xml_parent._cml_config
1537 if conf.i_ionic_vars:
1538 ionic_elt = model.xml_create_element(
u'ionic_current', NSS[
u'solver'])
1540 for var
in conf.i_ionic_vars:
1541 varelt = model.xml_create_element(
u'var', NSS[
u'solver'],
1542 content=var.fullname())
1543 ionic_elt.xml_append(varelt)
1544 solver_info.xml_append(ionic_elt)
1548 """Add the update equations for the linear ODEs.
1550 A linear ODE has the form du/dt = g+h.u where g & h are not functions of u. The
1551 update expression then looks like u = (u + g.dt)/(1 - h.dt).
1553 This replaces the linear_odes block
with the structure:
1558 <apply> <!-- (u + g.dt)/(1 - h.dt) --> </apply>
1565 block = getattr(self._solver_info, u'linear_odes',
None)
1566 dt = self.
_model.get_config().dt_variable.fullname()
1570 g.safe_remove_child(g, g.xml_parent)
1571 g_dt = mathml_apply.create_new(block,
u'times', [g, dt])
1572 numer = mathml_apply.create_new(block,
u'plus', [u.fullname(), g_dt])
1573 h.safe_remove_child(h, h.xml_parent)
1574 h_dt = mathml_apply.create_new(block,
u'times', [h, dt])
1575 denom = mathml_apply.create_new(block,
u'minus', [(
u'1',
u'dimensionless'), h_dt])
1576 eqn = mathml_apply.create_new(block,
u'divide', [numer, denom])
1577 math = block.xml_create_element(
u'math', NSS[
u'm'])
1578 math.xml_append(mathml_ci.create_new(block, u.fullname()))
1579 math.xml_append(mathml_ci.create_new(block, t.fullname()))
1580 math.xml_append(eqn)
1581 block.xml_append(math)
1584 block.xml_remove_child(block.math)
1587 """Link ci elements in the added XML to cellml_variable objects.
1589 This analyses the names in the ci elements to determine which variable
in
1590 the model they refer to.
1596 if hasattr(solver_info,
u'jacobian')
and hasattr(solver_info.jacobian,
u'math'):
1597 for elt
in solver_info.jacobian.math.apply:
1598 elt.classify_variables(root=
True)
1599 for elt
in solver_info.jacobian.math.apply:
1600 self.
_model.topological_sort(elt)
1605 """Check if any Jacobian entries need to be altered because the units of state variables have changed.
1607 If any variable considered a state variable by the Jacobian is now of type Computed then it has been
1608 converted. We figure out the conversion factor, update the Jacobian to reference the new state variable,
1609 and units-convert the derivative.
1614 def set_var_values(elt, vars=None):
1615 """Fake all variables appearing in the given expression being set to 1.0, and return them."""
1618 if isinstance(elt, mathml_ci):
1619 elt.variable.set_value(1.0)
1620 vars.append(elt.variable)
1622 for child
in getattr(elt,
'xml_children', []):
1623 set_var_values(child, vars)
1626 converted_state_vars = set()
1627 for entry
in getattr(self.
_solver_info.jacobian,
u'entry', []):
1629 if var.get_type() == VarTypes.Computed:
1630 converted_state_vars.add(var)
1631 if not converted_state_vars:
1635 for var
in converted_state_vars:
1636 defn = var.get_dependencies()[0]
1637 defn_vars = set_var_values(defn.eq.rhs)
1638 assert len(defn_vars) == 1,
"Unexpected form of units conversion expression found"
1639 factor = defn.eq.rhs.evaluate()
1640 state_var_map[var] = (defn_vars[0], factor)
1641 defn_vars[0].unset_values()
1643 for entry
in getattr(self.
_solver_info.jacobian,
u'entry', []):
1646 if var_i
in converted_state_vars:
1647 var_i, factor_i = state_var_map[var_i]
1648 var_i = var_i.get_source_variable(recurse=
True)
1649 entry.var_i = unicode(var_i.fullname())
1652 if var_j
in converted_state_vars:
1653 var_j, factor_j = state_var_map[var_j]
1654 var_j = var_j.get_source_variable(recurse=
True)
1655 entry.var_j = unicode(var_j.fullname())
1659 rhs = list(entry.math.xml_element_children())[0]
1660 entry.math.safe_remove_child(rhs)
1661 new_rhs = mathml_apply.create_new(entry,
'times', [(factor,
'dimensionless'), rhs])
1662 entry.math.xml_append(new_rhs)
1665 """Do a binding time analysis on the additional mathematics.
1672 """Apply func to each top-level mathematical construct in the solver info blocks.
1674 func must be able to accept mathml_piecewise, mathml_apply, mathml_ci and mathml_cn elements.
1678 if hasattr(solver_info,
u'jacobian'):
1679 if hasattr(solver_info.jacobian,
u'math'):
1680 for elt
in solver_info.jacobian.math.apply:
1682 for entry
in solver_info.jacobian.entry:
1683 for elt
in entry.math.xml_element_children():
1686 if hasattr(solver_info,
u'linear_odes'):
1687 for math
in solver_info.linear_odes.math:
1688 for elt
in math.xml_element_children():
1692 """Check if the solver info blocks contain any modifiable mathematics."""
1696 except StopIteration:
1700 """Get an iterable over mathematical constructs in the solver info blocks that can be changed.
1702 Returned elements will be mathml_piecewise, mathml_apply, mathml_ci or mathml_cn instances.
1706 if hasattr(solver_info,
u'jacobian'):
1707 if hasattr(solver_info.jacobian,
u'math'):
1708 for elt
in solver_info.jacobian.math.apply:
1710 for entry
in solver_info.jacobian.entry:
1711 for elt
in entry.math.xml_element_children():
1714 if hasattr(solver_info,
u'linear_odes'):
1720 """Return an iterable over the linearised ODEs, i.e. ODEs of the form
1721 du/dt = g + hu (with g, h
not functions of u).
1723 Yields tuples (u, t, eqns) where the form of eqns depends on whether
1724 add_linear_ode_update_equations has been called. If so, it
is a 1-tuple
1725 containing the update equation;
if not, it
is (g,h).
1730 u, t, eqn = list(math.xml_element_children())
1733 yield (u, t, (eqn,))
1736 u = ode.apply.ci.variable
1737 t = ode.apply.bvar.ci.variable
1738 opers = ode.apply[1].operands()
1740 h = opers.next().operands().next()
1744 """Recursively link ci elements in the given XML tree to cellml_variable objects.
1746 Also sets component links: for ci elements, to the component containing the linked
1747 variable,
and for cn elements, to the first component
in the model.
1749 if isinstance(elt, mathml_ci):
1751 elt._cml_variable = var
1752 elt._cml_component = var.component
1753 elif isinstance(elt, mathml_cn):
1755 elt._cml_component = elt.model.component
1756 elif hasattr(elt,
'xml_children'):
1757 for child
in elt.xml_children:
1760 _jac_temp_re = re.compile(
r't[0-9]+')
1762 """Return the variable in the model with name varname."""
1764 if varname ==
'delta_t':
1770 var = cellml_variable.get_variable_object(self.
_model, varname)
1772 raise ValueError(
"Cannot find variable '%s' referenced in SolverInfo" % varname)
1776 """Create the special 'dt' variable in the given component."""
1777 self.
_dt = modifier.add_variable(comp, modifier._uniquify_var_name(
u'dt', comp), units)
1778 self.
_dt._set_type(VarTypes.Free)
1782 """Get or create a special 'dt' variable."""
1788 """Get or create a special variable object that doesn't really exist in the model."""
1791 var = comp.get_variable_by_name(varname)
1793 var = cellml_variable.create_new(self.
_model, varname,
u'dimensionless')
1794 comp._add_variable(var)
1795 var._set_type(ptype)
1799 """Get or create a special component for containing special variables."""
1809 A container for configuration information, read
in from XML
1810 configuration files. The file structure
is described
in the
1811 read_configuration_file method.
1814 """Create a new store.
1816 doc specifies a CellML document, the processing of which this configuration store will affect.
1818 If given, options should be an optparse.Values instance containing command-line options.
1821 doc._cml_config = self
1849 """Read configuration stored in config_file.
1851 The configuration file is expected to be XML, conforming to
1852 the following structure. Currently little checking
is done on
1853 the file format; incorrectly formatted files are unlikely to
1854 give particularly helpful error messages.
1856 The root element may contain a
'global' element, giving
global
1857 configuration options. These include:
1860 Contains one
or more
'lookup_table' elements, one
for each
1861 type of lookup table available. These contain (a selection of)
1863 *
'var' - the variable to key on. The component name given
1864 should be that
from which the variable
is exported. Must be
1866 *
'min',
'max',
'step' - table bounds parameters. Optional.
1867 Default values are used
for parameters that are
not present.
1869 Defines which variables hold the ionic
and stimulus currents,
1870 if any. It contains 2 elements:
1871 *
'stimulus' - the full name of the stimulus current variable
1872 *
'ionic_match' - a regular expression matching full names of
1873 ionic current variables. It may also match the stimulus
1874 current, but the stimulus will never be considered an ionic
1875 current. The value
is split on
','; the first part
is then
1876 matched against component names,
and the second against
1877 variables
in matching components.
1879 This
is mostly redundant now, because the equation
for dV/dt
1880 is used first to determine the ionic currents (see documentation
1881 for _find_transmembrane_currents_from_voltage_ode),
and only
1882 if this fails to find suitable currents will the ionic_match
1884 *
'transmembrane_potential'
1885 Defines which variable holds the transmembrane potential.
1886 Defaults to
'membrane,V' if not present.
1887 *
'membrane_capacitance'
1888 Defines which variable holds the cell membrane capacitance.
1890 The root element also contains 0
or more
'for_model' elements,
1891 which contain settings
for individual models. These must have
1892 at least one of an
'id' or 'name' attribute, which specify the
1893 model
in question. They can also contain anything allowable
as
1894 global configuration options. Options given here override those
1897 Configuration which
is identical
for groups of models may be given
1898 using the
'for_models' element. This has an
'ids' element
as its
1899 first child, which contains
'id' elements holding either the name
1900 or id of a model. The remaining contents of the
'for_models'
1901 element are
as for 'for_model'.
1903 There are 3 ways of specifying variables:
1904 1. By name (var type=
'name')
1905 Variable names are given
in full form, i.e. component_name,variable_name
1906 2. By standardised name (var type=
'oxmeta')
1907 Use the name
from the oxmeta annotations
1908 3. By reference to a section of the config file (when defining lookup table keys)
1909 e.g. <var type=
'config-name'>transmembrane_potential</var>
1911 Within any element that specifies a variable, a list of <var> elements can be
1912 provided. Each will be tried
in turn to see
if a match can be found
in the model,
1913 and the first match wins.
1915 Some items are overridden
if oxmeta annotations are present
in the model,
with
1916 the annotated variable taking precedence over the config file specification.
1918 DEBUG('config',
"Reading configuration from ", config_file)
1919 binder = amara.bindery.binder()
1920 binder.set_binding_class(
None,
"units", cellml_units)
1921 binder.set_binding_class(
None,
"unit", cellml_unit)
1922 rules = [bt.ws_strip_element_rule(
u'*')]
1923 config_doc =
amara_parse(config_file, rules=rules, binderobj=binder)
1925 for defn
in config_doc.xml_xpath(
u'/*/units'):
1929 if self.
options and hasattr(config_doc.pycml_config,
'command_line_args'):
1930 args = map(str, config_doc.pycml_config.command_line_args.arg)
1931 args.append(
'dummy-file')
1936 glo = config_doc.xml_xpath(
u'/*/global')
1938 sections.append(glo[0])
1942 model_id = getattr(self.
doc.model,
u'id', self.
doc.model.name)
1943 sections.extend(config_doc.xml_xpath(
1944 u'/*/for_models[ids/id="%s" or ids/id="%s"]'
1945 % (self.
doc.model.name, model_id)))
1946 sections.extend(config_doc.xml_xpath(
1947 u'/*/for_model[@name="%s" or @id="%s"]'
1948 % (self.
doc.model.name, model_id)))
1950 for section
in sections:
1951 if hasattr(section,
u'lookup_tables'):
1953 if hasattr(section,
u'currents'):
1955 if hasattr(section,
u'transmembrane_potential'):
1956 self.
_parse_Vm(section.transmembrane_potential)
1957 if hasattr(section,
u'membrane_capacitance'):
1958 self.
_parse_Cm(section.membrane_capacitance)
1961 """Having read all the configuration files, apply to the model."""
1964 config_key = (
'config-name',
'transmembrane_potential')
1974 """Create a variable definition object."""
1975 xml_fragment =
'<var type="%s">%s</var>' % (defn_type, content)
1976 return amara.parse(str(xml_fragment)).var
1979 """Check a variable definition is syntactically valid.
1981 If type == 'name', it must have text content of the form
"component_name,variable_name".
1982 If type ==
'oxmeta', it must have text content drawn
from METADATA_NAMES.
1983 If type ==
'config-name', it must have text content either
'stimulus' or 'transmembrane_potential'.
1985 defn_type = getattr(var_elt, u'type',
u'name')
1986 if defn_type ==
u'name':
1987 name_parts = unicode(var_elt).strip().split(
',')
1988 if len(name_parts) != 2:
1991 elif defn_type ==
u'oxmeta':
1992 if unicode(var_elt)
not in cellml_metadata.METADATA_NAMES:
1994 elif defn_type ==
u'config-name':
1995 if unicode(var_elt)
not in [
u'stimulus',
u'transmembrane_potential',
u'membrane_capacitance']:
1996 raise ConfigurationError(
'"' + unicode(var_elt) +
'" is not a name known to the config file')
1998 raise ConfigurationError(
'"' + defn_type +
'" is not a valid variable definition type')
2002 """Parse definition of a special variable."""
2003 if hasattr(elt,
'var'):
2006 for vardef
in elt.var:
2016 """Parse definition of variable holding the transmembrane potential."""
2020 """Parse definition of variable holding the cell membrane capacitance."""
2024 """Parse definitions of ionic and stimulus currents."""
2025 if hasattr(currents,
u'stimulus'):
2027 if hasattr(currents,
u'ionic_match'):
2032 """Find a variable matching the given definition.
2034 If pe_done is True, then partial evaluation has been performed
2035 on the model, so looking
for variables by name needs to look
for
2036 variables called compname__varname
in the single component.
2038 defn_type = getattr(defn, u'type',
u'name')
2039 if defn_type ==
u'name':
2040 name_parts = unicode(defn).strip().split(
',')
2043 var = self.
doc.model.component.get_variable_by_name(
u'__'.join(name_parts))
2047 var = self.
doc.model.xml_xpath(
u'cml:component[@name="%s"]/cml:variable[@name="%s"]'
2048 % tuple(name_parts))
2051 elif defn_type ==
u'oxmeta':
2052 var = self.
doc.model.get_variable_by_oxmeta_name(str(defn), throw=
False)
2053 elif defn_type ==
u'config-name':
2054 if unicode(defn) ==
u'stimulus':
2056 elif unicode(defn) ==
u'transmembrane_potential':
2058 elif unicode(defn) ==
u'membrane_capacitance':
2061 raise ConfigurationError(
'"' + str(defn) +
'" is not a valid configuration file variable name')
2063 raise ConfigurationError(
'"' + defn_type +
'" is not a valid variable definition type')
2067 """Recursively apply func to any ci elements in the tree rooted at elt."""
2068 if isinstance(elt, mathml_ci):
2071 for child
in getattr(elt,
'xml_children', []):
2075 """Analyse the expression for dV/dt to determine the transmembrane currents.
2077 Looks for an equation defining dV/
d(something)
and assumes the something
is
2078 time; this will be checked during code generation
for Chaste. It then finds
2079 all variables on the RHS of this equation which have the same units
as the
2080 stimulus current (self.
i_stim_var)
and identifies these
as transmembrane
2081 currents. Will automatically exclude the stimulus current.
2083 If self.
V_variable is not set, returns the empty list.
2086 DEBUG(
'config',
"Transmembrane potential not configured, so can't determine currents from its ODE")
2091 from CellMLToNektarTranslator
import CellMLToNektarTranslator
2092 current_units = CellMLToNektarTranslator.get_current_units_options(self.
doc.model)
2095 def find_units_match(test_units, units_list, remove_match=False, keep_only_match=False):
2096 """Look for a units definition dimensionally equivalent to test_units within units_list.
2098 If remove_match is True, remove the first match
from the list.
2099 If keep_only_match
is True, remove all entries
except the first match
from the list.
2100 Return the matching units,
or None if there are no matches.
2102 for units
in units_list:
2103 if test_units.dimensionally_equivalent(units):
2108 if match
and remove_match:
2109 units_list.remove(match)
2110 if match
and keep_only_match:
2111 units_list[:] = [match]
2114 def clear_values(expr, process_definitions=False):
2115 """Recursively clear saved values for variables in this expression.
2117 If process_definitions is True, recursively treat expressions defining variables
2118 used
in this expression, too.
2120 def process_var(var):
2122 var._unset_binding_time(only_temporary=
True)
2123 if process_definitions:
2124 defn = var.get_dependencies()
2126 if isinstance(defn[0], mathml_apply):
2127 clear_values(defn[0].eq.rhs, process_definitions=
True)
2128 elif isinstance(defn[0], cellml_variable):
2129 process_var(defn[0])
2130 def process_ci(ci_elt):
2131 process_var(ci_elt.variable)
2134 def check_if_current(ci_elt, vars_found):
2135 """Check if this is a transmembrane current."""
2137 if v.get_source_variable(recurse=
True)
is not self.
i_stim_var:
2138 vars_found.append(v)
2140 u = v.component.get_units_by_name(v.units)
2141 if find_units_match(u, current_units, keep_only_match=
True):
2142 ionic_vars.append(v.get_source_variable(recurse=
True))
2143 ionic_vars[-1]._cml_ref_in_dvdt = ci_elt
2145 if not v.is_statically_const(ignore_annotations=
True):
2148 def bfs(func, vars, *args, **kwargs):
2149 """Do a breadth first search of the definitions of variables in vars.
2151 func is the recursive function to call. It will be given the list of defining expressions
2152 as its first argument,
and args
and kwargs
as remaining arguments.
2155 defn = var.get_dependencies()
2157 var._set_binding_time(BINDING_TIMES.static, temporary=
True)
2158 if isinstance(defn[0], cellml_variable):
2159 defn = get_defn(defn[0])
2161 assert isinstance(defn[0], mathml_apply)
2163 defn = defn[0].eq.rhs
2167 defn = get_defn(var)
2171 func(defns, *args, **kwargs)
2173 def find_currents(exprs, depth=0, maxdepth=2):
2174 """Find ionic currents by searching the given expressions.
2176 On the initial call, exprs should contain just the definition of dV/dt (i.e. the RHS).
2178 Uses breadth-first search of the equation dependency tree to find variables that
2179 have units dimensionally equivalent to one of the current formulations that Chaste
2180 can handle, or equivalent to the stimulus current
's units if one is defined.
2182 Initially, A_per_F is removed
from the list, since the RHS of dV/dt should always
2183 have equivalent dimensions. If another option can
't be found within maxdepth levels,
2184 we restart the search with A_per_F included. The depth limit
is intended to guard against
2185 unexpectedly finding something that isn
't a current; it's somewhat dodgy, but won
't
2186 break on any model I know,
and I haven
't thought of a better approach yet.
2188 When one variable with suitable units
is found, further ionic currents must have units
2189 equivalent to its to be found. Also once one ionic current
is found, only the remaining
2190 expressions at its depth will be processed.
2192 if depth == 0
and maxdepth > 0:
2193 dvdt_units = exprs[0].xml_parent.eq.lhs.get_units()
2194 A_per_F = find_units_match(dvdt_units, current_units, remove_match=
True)
2202 if not ionic_vars
and depth != maxdepth:
2204 bfs(find_currents, vars_found, depth+1, maxdepth)
2206 if not ionic_vars
and depth == 0
and maxdepth > 0
and A_per_F:
2207 current_units.append(A_per_F)
2208 find_currents(exprs, depth, maxdepth=-1)
2210 def assign_values_for_stimulus_check(exprs, found_stim=Sentinel()):
2211 """Assign temporary values to variables in order to check the stimulus sign.
2213 This will process defining expressions in a breadth first search until the stimulus
2214 current
is found. Each variable that doesn
't have its definitions processed will
2215 be given a value as follows:
2216 - stimulus current = 1
2217 - other currents = 0
2218 - other variables = 1
2219 The stimulus current
is then negated
from the sign expected by Chaste
if evaluating
2220 dV/dt gives a positive value.
2222 assert len(current_units) == 1
2226 if v.get_source_variable(recurse=
True)
is self.
i_stim_var:
2230 u = v.component.get_units_by_name(v.units)
2231 if u.dimensionally_equivalent(current_units[0]):
2233 elif not v.is_statically_const(ignore_annotations=
True):
2239 bfs(assign_values_for_stimulus_check, vars, found_stim=found_stim)
2242 for expr
in (e
for e
in self.
doc.model.get_assignments()
if isinstance(e, mathml_apply)
and e.is_ode()):
2244 (dep_var, time_var) = expr.assigned_variable()
2245 if dep_var.get_source_variable(recurse=
True)
is self.
V_variable:
2247 find_currents([expr.eq.rhs])
2253 clear_values(expr.eq.rhs, process_definitions=
True)
2256 assign_values_for_stimulus_check([expr.eq.rhs])
2258 clear_values(expr.eq.rhs, process_definitions=
True)
2261 DEBUG(
'config',
"Found ionic currents from dV/dt: ", ionic_vars)
2267 """Find the variable object in the model for a particular concept.
2269 Will look for a variable annotated
with the given oxmeta_name first, then
2270 try the list of definitions
from the configuration file
in turn.
2275 for defn
in [oxmeta_defn] + definitions:
2282 """Find the variables representing currents."""
2284 if not self.
doc.model.is_self_excitatory():
2289 msg =
"No stimulus current found; you'll have trouble generating Nektar code"
2290 if self.
options.fully_automatic:
2293 print >>sys.stderr, msg
2296 if not self.
options.use_i_ionic_regexp:
2300 if getattr(defn,
u'type',
u'name') !=
u'name':
2302 regexps = unicode(defn).strip().split(
',')
2303 comp_re = re.compile(regexps[0] +
'$')
2304 var_re = re.compile(regexps[1] +
'$')
2305 for component
in getattr(self.
doc.model,
u'component', []):
2306 if comp_re.match(unicode(component.name).strip()):
2307 for var
in getattr(component,
u'variable', []):
2309 var_re.match(unicode(var.name).strip())):
2312 msg =
"No ionic currents found; you'll have trouble generating Nektar code"
2313 if self.
options.fully_automatic:
2316 print >>sys.stderr, msg
2320 """Parse a lookup_tables element."""
2321 for lt
in lookup_tables.lookup_table:
2322 var_type = getattr(lt.var,
u'type',
u'name')
2323 var_name = unicode(lt.var).strip()
2324 config_key = (var_type, var_name)
2328 for elt
in lt.xml_element_children():
2329 if elt.localName !=
u'var':
2330 self.
lut_config[config_key][
'table_' + elt.localName] = unicode(elt).strip()
2331 if hasattr(lt,
u'units'):
2335 raise ConfigurationError(
'The units "%s" referenced by the lookup table for "%s" do not exist'
2336 % (lt.units, var_name))
2337 self.
lut_config[config_key][
'table_units'] = units
2341 """Set default configuration for a lookup table."""
2342 def_dict = optimize.LookupTableAnalyser._LT_DEFAULTS
2343 for k, v
in def_dict.iteritems():
2344 if k !=
'table_var':
2346 lut_dict[
'table_units'] =
None
2350 """Annotate ionic & stimulus current vars so PE doesn't remove them.
2351 Also annotate the membrane capacitance, if defined.
"""
2355 var.set_pe_keep(
True)
2361 """Expose variables for access with GetAnyVariable if desired."""
2364 if t == VarTypes.Constant:
2365 var.set_is_modifiable_parameter(
True)
2366 elif t
in [VarTypes.Computed, VarTypes.Free, VarTypes.Mapped]:
2367 var.set_is_derived_quantity(
True)
2368 if self.
options.expose_annotated_variables:
2370 if (
not self.
options.use_chaste_stimulus
or
2371 not var.oxmeta_name
in cellml_metadata.STIMULUS_NAMES):
2373 DEBUG(
'translate',
"+++ Exposed annotated variables")
2374 if self.
options.expose_all_variables:
2375 for var
in self.
doc.model.get_all_variables():
2377 DEBUG(
'translate',
"+++ Exposed all variables")
2380 "Annotate all vars tagged with metadata so PE doesn't remove them."
2382 var.set_pe_keep(
True)
2386 """Find and store the variable object representing V.
2388 Tries metadata annotation first. If that fails, uses the name given in
2389 the command line options,
if present. If that fails, uses the config file.
2392 raise ValueError(
'No command line options given')
2394 if self.
options.transmembrane_potential:
2397 raise ConfigurationError(
'The name of V must contain both component and variable name')
2401 raise ConfigurationError(
'No transmembrane potential found; check your configuration')
2405 """Find and store the variable object representing the cell membrane capacitance.
2407 Uses first metadata, if present, then the configuration file.
"""
2412 """Find the variable objects used as lookup table keys.
2414 This method translates the variable names given in the configuration file into objects
2415 in the document,
and then uses those objects
as keys
in our lut_config dictionary.
2416 The ultimate source variable
for the variable specified
is used,
in order to avoid
2417 complications caused by intermediaries being removed (e.g. by PE).
2419 The table settings are also units-converted to match the units of the key variable.
2423 defn_type, content = key
2428 LOG(
'lookup-tables', logging.WARNING,
'Variable', content,
'not found, so not using as table index.')
2430 var = var.get_source_variable(recurse=
True)
2431 if not var
in new_config:
2432 new_config[var] = {}
2435 table_units = new_config[var][
'table_units']
2437 var_units = var.get_units()
2438 if not table_units.dimensionally_equivalent(var_units):
2439 LOG(
'lookup-tables', logging.WARNING,
'Variable', content,
'is in units', var_units.description(),
2440 'which are incompatible with', table_units.description(),
'so not using as table index.')
2441 elif not table_units.equals(var_units):
2444 print 'LT conversion:', table_units.description(),
'to', var_units.description(),
'equal?', table_units.equals(var_units)
2445 m = table_units.get_multiplicative_factor() / var_units.get_multiplicative_factor()
2446 for setting
in new_config[var]:
2448 old_value = float(new_config[var][setting])
2449 new_value = m * (old_value - table_units.get_offset()) + var_units.get_offset()
2450 new_config[var][setting] = unicode(new_value)
2451 print 'LT conversion', setting, old_value, new_value
2452 except (ValueError, TypeError):
2455 DEBUG(
'config',
'Lookup tables configuration:', new_config)
2460 """Check that the metadata annotations are 'sensible'.
2462 Ensures that only names we know are used, and that the same name isn
't used for multiple variables.
2464 vars = cellml_metadata.find_variables(self.doc.model, ('bqbiol:is', NSS[
'bqbiol']))
2468 names_used = [var.oxmeta_name
for var
in self.
metadata_vars]
2469 DEBUG(
'metadata',
'Names found: ', names_used)
2471 unknown_names = frozenset(names_used) - cellml_metadata.METADATA_NAMES
2473 msg = [
'Unrecognised oxmeta variable names found (run with --assume-valid to ignore):']
2474 msg.extend(sorted(unknown_names))
2478 for name
in names_used:
2480 raise ConfigurationError(name +
' metadata attribute is duplicated in the cellml file.')
2490 """get_options(args):
2491 Process our command-line options.
2493 args is a list of options & positional arguments.
2495 default_options,
if given,
is an instance of optparse.Values created by a
2496 previous call to this function.
2498 usage = 'usage: %prog [options] <cellml file or URI>'
2499 parser = optparse.OptionParser(version=
"%%prog %s" % __version__,
2501 parser.add_option(
'-q',
'--quiet', action=
'store_true', default=
False,
2502 help=
"don't show warning messages, only errors")
2504 parser.add_option(
'-T',
'--translate',
2505 dest=
'translate', action=
'store_true',
2507 help=
"output computer code [default]")
2508 parser.add_option(
'-C',
'--output-cellml',
2509 dest=
'translate', action=
'store_false',
2510 help=
"output an annotated CellML file instead of translating, on stdout unless -o specified")
2511 translators = sorted(CellMLTranslator.translators)
2512 parser.add_option(
'-t',
'--translate-type',
2513 type=
'choice', choices=translators,
2514 default=
'Nektar', metavar=
'TYPE',
2515 help=
"the type of code to output [default: %default]. "
2516 "Choices: " + str(translators))
2517 parser.add_option(
'-o', dest=
'outfilename', metavar=
'OUTFILE',
2518 help=
"write program code to OUTFILE [default action is to use the input filename with a different extension]")
2520 parser.add_option(
'--config-file',
2521 action=
'append', default=[],
2522 help=
"pathname of configuration file")
2523 parser.add_option(
'-A',
'--fully-automatic',
2524 action=
'store_true', default=
False,
2525 help=
"if human intervention is required, fail noisily")
2526 parser.add_option(
'--assume-valid',
2527 action=
'store_true', default=
False,
2528 help=
"skip some of the model validation checks")
2529 parser.add_option(
'--warn-on-unit-conversions',
2530 action=
'store_true', default=
False,
2531 help=
"generate a warning if unit conversions are required")
2532 parser.add_option(
'--Wu',
'--warn-on-units-errors',
2533 action=
'store_true', default=
False,
2534 dest=
'warn_on_units_errors',
2535 help=
"give a warning instead of an error for dimensional inconsistencies")
2536 parser.add_option(
'-V',
'--transmembrane-potential', default=
None, metavar=
'POT_VAR',
2537 help=
"POT_VAR is the full name of the variable representing the transmembrane potential."
2538 " If not specified here, the configuration file will be used, which is the prefered method."
2539 " Defaults to 'membrane,V'.")
2540 parser.add_option(
'-d',
'--debug', action=
'store_true', default=
False,
2541 help=
"output debug info to stderr")
2542 parser.add_option(
'-D',
'--debug-source', action=
'append',
2543 help=
"only show debug info from the specified part of the code."
2544 " This option may appear more than once to select multiple sources. Implies -d.")
2545 parser.add_option(
'--profile', action=
'store_true', default=
False,
2546 help=
"turn on profiling of PyCml")
2554 group = optparse.OptionGroup(parser,
'Transformations',
2555 "These options control which transformations (typically optimisations) are applied in the generated code")
2556 group.add_option(
'-l',
'--lookup-tables',
2557 dest=
'lut', action=
'store_true', default=
False,
2558 help=
"perform a lookup table analysis")
2559 group.add_option(
'-p',
'--pe',
'--partial-evaluation',
2560 dest=
'pe', action=
'store_true', default=
False,
2561 help=
"partially evaluate the model")
2562 group.add_option(
'-u',
'--units-conversions',
2563 action=
'store_true', default=
False,
2564 help=
"add explicit units conversion mathematics")
2565 group.add_option(
'-j',
'--maple-output',
2566 metavar=
'FILENAME', default=
None,
2567 help=
"file containing output from a Maple script generated using -J. The generated"
2568 " code/CellML will then contain a symbolic Jacobian as computed by Maple.")
2569 group.add_option(
'-J',
'--do-jacobian-analysis',
2570 action=
'store_true', default=
False,
2571 help=
"generate code to perform Jacobian analysis for backward Euler & CVODE; implies -t Maple")
2572 group.add_option(
'--backward-euler',
2573 action=
'store_true', default=
False,
2574 help=
"generate a specialised cell model that solves itself using a decoupled"
2575 " backward Euler method. Not compatible with --rush-larsen. Implies -t Chaste."
2577 group.add_option(
'--rush-larsen',
2578 action=
'store_true', default=
False,
2579 help=
"use the Rush-Larsen method to solve Hodgkin-Huxley style gating variable"
2580 " equations. Not compatible with --backward-euler. Implies -t Chaste.")
2581 group.add_option(
'--grl1',
2582 action=
'store_true', default=
False,
2583 help=
"use the GRL1 method to solve Hodgkin-Huxley style gating variable"
2584 " equations. Not compatible with the backward Euler transformation."
2585 " Implies -t Chaste.")
2586 group.add_option(
'--grl2',
2587 action=
'store_true', default=
False,
2588 help=
"use the GRL2 method to solve Hodgkin-Huxley style gating variable"
2589 " equations. Not compatible with the backward Euler transformation."
2590 " Implies -t Chaste.")
2591 parser.add_option_group(group)
2593 group = optparse.OptionGroup(parser,
'Generated code options')
2594 group.add_option(
'-c',
'--class-name', default=
None,
2595 help=
"explicitly set the name of the generated class")
2596 group.add_option(
'-a',
'--augment-class-name',
2597 dest=
'augment_class_name', action=
'store_true',
2599 help=
"alter the class name to show what transformations are used")
2600 group.add_option(
'--no-timestamp',
2601 action=
'store_true', default=
False,
2602 help=
"don't add a timestamp comment to generated files")
2603 parser.add_option_group(group)
2605 group = optparse.OptionGroup(parser,
'Maple options',
"Options specific to Maple code output")
2606 group.add_option(
'--dont-omit-constants',
2607 dest=
'omit_constants', action=
'store_false', default=
True,
2608 help=
"when generating Maple code, include assignments of constants")
2609 group.add_option(
'--compute-partial-jacobian', dest=
'compute_full_jacobian',
2610 action=
'store_false', default=
True,
2611 help=
"make generated Maple code compute a Jacobian specific to a Newton solve"
2612 " of the nonlinear portion of the ODE system, rather than the full system Jacobian")
2613 parser.add_option_group(group)
2615 group = optparse.OptionGroup(parser,
'Python options',
"Options specific to Python code output")
2616 group.add_option(
'--no-numba', dest=
'numba', default=
True, action=
'store_false',
2617 help=
"turn off using Numba to optimise code on-the-fly")
2618 parser.add_option_group(group)
2620 group = optparse.OptionGroup(parser,
'Chaste options',
"Options specific to Chaste code output")
2621 group.add_option(
'-y',
'--dll',
'--dynamically-loadable',
2622 dest=
'dynamically_loadable',
2623 action=
'store_true', default=
False,
2624 help=
"add code to allow the model to be compiled to a shared library and dynamically loaded"
2625 " (only works if -t Chaste is used)")
2626 group.add_option(
'--use-chaste-stimulus',
2627 action=
'store_true', default=
False,
2628 help=
"when generating Chaste code, use Chaste's stimulus rather than that defined in the model")
2629 group.add_option(
'--no-use-chaste-stimulus', dest=
'use_chaste_stimulus',
2630 action=
'store_false',
2631 help=
"when generating Chaste code, use the model's stimulus, not Chaste's")
2632 group.add_option(
'-i',
'--convert-interfaces',
2633 action=
'store_true', default=
False,
2634 help=
"perform units conversions at interfaces to Chaste (only works if -t Chaste is used)")
2635 group.add_option(
'--use-i-ionic-regexp', dest=
'use_i_ionic_regexp',
2636 action=
'store_true', default=
False,
2637 help=
"determine ionic currents from the regexp specified in the config file"
2638 " rather than analysing the voltage derivative equation")
2639 group.add_option(
'--include-dt-in-tables',
2640 action=
'store_true', default=
False,
2641 help=
"[experimental] allow timestep to be included in lookup tables. By default"
2642 " uses the timestep of the first cell created. Requires support from external"
2643 " code if timestep changes. Only really useful for backward Euler cells.")
2644 group.add_option(
'-m',
'--use-modifiers',
2645 action=
'store_true', default=
False,
2646 help=
"[experimental] add modifier functions for certain"
2647 " metadata-annotated variables for use in sensitivity analysis (only works if -t Chaste is used)")
2648 group.add_option(
'--use-data-clamp',
2649 action=
'store_true', default=
False,
2650 help=
"[experimental] generate a data clamp subclass of CVODE cells"
2651 " which contains data clamp currents for fitting experimental data (only works if -t CVODE is used)")
2652 group.add_option(
'--expose-annotated-variables',
2653 action=
'store_true', default=
False,
2654 help=
"expose all oxmeta-annotated variables for access via the GetAnyVariable functionality")
2655 group.add_option(
'--expose-all-variables',
2656 action=
'store_true', default=
False,
2657 help=
"expose all variables for access via the GetAnyVariable functionality")
2658 parser.add_option_group(group)
2660 group = optparse.OptionGroup(parser,
'Functional Curation options',
"Options specific to use by Functional Curation")
2661 def protocol_callback(option, opt_str, value, parser):
2663 Protocols don't always produce normal cardiac cell models.
2664 However, we want to allow a later option to override these changes.
2666 parser.values.protocol = value
2667 parser.values.convert_interfaces = False
2668 parser.values.use_chaste_stimulus =
False
2669 group.add_option(
'--protocol',
2670 action=
'callback', callback=protocol_callback, type=
'string', nargs=1,
2671 help=
"specify a simulation protocol to apply to the model prior to translation")
2672 group.add_option(
'--protocol-options', action=
'store', type=
'string',
2673 help=
"extra options for the protocol")
2674 group.add_option(
'--expose-named-parameters',
2675 action=
'store_true', default=
False,
2676 help=
"expose all constant variables with 'name' annotations for access as model parameters")
2677 parser.add_option_group(group)
2679 group = optparse.OptionGroup(parser,
'Lookup tables options',
"Options specific to the lookup tables optimisation")
2680 lookup_type_choices = [
'entry-below',
'nearest-neighbour',
'linear-interpolation']
2681 group.add_option(
'--lookup-type', choices=lookup_type_choices,
2682 default=
'linear-interpolation',
2683 help=
"the type of table lookup to perform [default: %default]."
2684 " Choices: " + str(lookup_type_choices))
2685 group.add_option(
'--no-separate-lut-class', dest=
'separate_lut_class',
2686 action=
'store_false', default=
True,
2687 help=
"don't put lookup tables in a separate class")
2688 group.add_option(
'--row-lookup-method',
2689 action=
'store_true', default=
True,
2690 help=
"add and use a method to look up a whole row of a table")
2691 group.add_option(
'--no-row-lookup-method', dest=
'row_lookup_method',
2692 action=
'store_false',
2693 help=
"don't add and use a method to look up a whole row of a table")
2694 group.add_option(
'--combine-commutative-tables',
2695 action=
'store_true', default=
False,
2696 help=
"optimise a special corner case to reduce the number of tables."
2697 " See documentation for details.")
2698 group.add_option(
'--lt-index-uses-floor',
2699 action=
'store_true', default=
False,
2700 help=
"use floor() to calculate LT indices, instead of just casting")
2701 group.add_option(
'--constrain-table-indices',
2702 action=
'store_true', default=
False,
2703 help=
"constrain lookup table index variables to remain within the bounds specified,"
2704 " rather than throwing an exception if they go outside the bounds")
2705 group.add_option(
'--no-check-lt-bounds', dest=
'check_lt_bounds',
2706 action=
'store_false', default=
True,
2707 help=
"[unsafe] don't check for LT indexes going outside the table bounds")
2708 parser.add_option_group(group)
2710 group = optparse.OptionGroup(parser,
'Partial evaluation options',
"Options specific to the partial evaluation optimisation")
2711 group.add_option(
'--pe-convert-power',
2712 action=
'store_true', default=
False,
2713 help=
"convert pow(x,3) to x*x*x; similarly for powers 2 & 4.")
2714 group.add_option(
'--no-partial-pe-commutative', dest=
'partial_pe_commutative',
2715 action=
'store_false', default=
True,
2716 help=
"don't combine static operands of dynamic commutative associative applys")
2717 group.add_option(
'--no-pe-instantiate-tables', dest=
'pe_instantiate_tables',
2718 action=
'store_false', default=
True,
2719 help=
"don't instantiate definitions that will be tables regardless of usage")
2720 parser.add_option_group(group)
2722 options, args = parser.parse_args(args, values=default_options)
2724 parser.error(
"exactly one input CellML file must be specified")
2727 if options.debug_source:
2728 options.debug =
True
2729 if options.do_jacobian_analysis:
2730 options.translate_type =
'Maple'
2731 options.maple_output =
False
2732 options.rush_larsen =
False
2733 options.backward_euler =
False
2734 if options.backward_euler:
2735 if not options.maple_output:
2736 parser.error(
"Backward Euler code generation requires maple output (-j)")
2737 options.rush_larsen =
False
2738 options.grl1 =
False
2739 options.grl2 =
False
2740 if options.rush_larsen
or options.backward_euler
or options.grl1
or options.grl2:
2741 options.translate_type =
'Chaste'
2742 if options.use_data_clamp
and not options.translate_type==
'CVODE':
2743 parser.error(
"Data clamp option '--use-data-clamp' also requires CVODE ('-t CVODE'). If you are calling this via ConvertCellModel use '--cvode-data-clamp'.")
2749 options.numba =
False
2751 return options, args[0]
2755 """Load and validate a CellML model."""
2757 logging.thread =
None
2759 formatter = logging.Formatter(fmt=
"%(name)s: %(message)s")
2760 handler = logging.StreamHandler(sys.stderr)
2761 handler.setFormatter(formatter)
2763 if options.debug_source:
2765 logging.getLogger().addHandler(handler)
2766 logging.getLogger().setLevel(logging.DEBUG)
2769 notifier =
NotifyHandler(level=logging.WARNING_TRANSLATE_ERROR)
2770 logging.getLogger(
'validator').addHandler(notifier)
2772 valid, doc = v.validate(model_file, return_doc=
True, show_warnings=
not options.quiet,
2773 check_for_units_conversions=options.warn_on_unit_conversions,
2774 warn_on_units_errors=options.warn_on_units_errors,
2775 assume_valid=options.assume_valid)
2779 if not valid
or notifier.messages:
2780 print >>sys.stderr, model_file,
2782 print >>sys.stderr,
"is not a valid CellML file"
2784 print >>sys.stderr,
"contains untranslatable constructs (see warnings above for details)"
2790 """Translate the file given on the command line."""
2793 DEBUG(
'translate',
"+++ Loaded model")
2796 for config_file
in options.config_file:
2797 config.read_configuration_file(config_file)
2798 DEBUG(
'translate',
"+++ Read config")
2801 if options.protocol:
2803 protocol.apply_protocol_file(doc, options.protocol)
2805 post_proto_cellml = options.outfilename
or model_file
2806 post_proto_cellml = os.path.splitext(post_proto_cellml)[0] +
'-proto.cellml.ppp'
2808 doc.xml(indent=
u'yes', stream=stream)
2810 DEBUG(
'translate',
"+++ Applied protocol")
2812 config.finalize_config()
2813 DEBUG(
'translate',
"+++ Processed config")
2818 translator_klass = CellMLTranslator.translators[options.translate_type]
2819 if not options.protocol:
2820 translator_klass.generate_interface(doc, solver_info)
2821 config.validate_metadata(options.assume_valid)
2822 DEBUG(
'translate',
"+++ Generated interface")
2825 config.find_lookup_variables()
2826 DEBUG(
'translate',
"+++ Found LT keys")
2831 config.annotate_currents_for_pe()
2833 config.annotate_metadata_for_pe()
2834 DEBUG(
'translate',
"+++ Annotated variables")
2836 config.expose_variables()
2838 class_name = options.class_name
2840 class_name = doc.model.name.replace(
'-',
'_')
2841 if options.augment_class_name:
2842 class_name =
u'CML_' + class_name
2846 class_name +=
'_lut'
2847 if options.backward_euler:
2849 if options.use_modifiers:
2850 class_name +=
'_sens'
2851 if options.protocol:
2853 class_name +=
"_Proto_" + os.path.splitext(os.path.basename(options.protocol))[0]
2855 output_filename = getattr(options,
'outfilename',
None)
2856 if not options.translate
and not output_filename:
2857 output_filename =
'stdout'
2859 if options.units_conversions:
2860 doc.model.add_units_conversions()
2861 DEBUG(
'translate',
"+++ Added units conversions")
2863 if options.do_jacobian_analysis:
2865 lin.analyse_for_jacobian(doc, V=config.V_variable)
2866 DEBUG(
'translate',
"+++ Analysed model for Jacobian")
2868 if options.maple_output:
2870 from maple_parser
import MapleParser
2872 jacobian_file = file(options.maple_output)
2873 doc.model._cml_jacobian = mp.parse(jacobian_file)
2874 doc.model._cml_jacobian_full = mp.JacobianWasFullSize
2875 jacobian_file.close()
2876 if not options.backward_euler
and doc.model._cml_jacobian_full:
2878 solver_info.add_jacobian_matrix()
2879 solver_info.add_variable_links()
2881 if options.backward_euler:
2884 lin.analyse_for_jacobian(doc, V=config.V_variable)
2885 lin.rearrange_linear_odes(doc)
2887 jacobian = doc.model._cml_jacobian
2888 if isinstance(jacobian, tuple):
2889 assert doc.model._cml_jacobian_full
2890 jacobian = jacobian[1]
2891 nonlinear_vars = set([v.get_source_variable(recurse=
True)
for v
in doc.model._cml_nonlinear_system_variables])
2893 return cellml_variable.get_variable_object(doc.model, vname).get_source_variable(recurse=
True)
2894 for var_i, var_j
in jacobian.keys():
2895 if gv(var_i)
not in nonlinear_vars
or gv(var_j)
not in nonlinear_vars:
2896 del jacobian[(var_i, var_j)]
2897 if doc.model._cml_jacobian_full:
2900 for key, expr
in jacobian.iteritems():
2902 if key[0] == key[1]:
2904 new_expr = maple_parser.MNumber([
'1'])
2905 if not (isinstance(expr, maple_parser.MNumber)
and str(expr) ==
'0'):
2909 args.append(new_expr)
2910 args.append(maple_parser.MOperator([maple_parser.MVariable([
'delta_t']), expr],
'prod',
'times'))
2911 new_expr = maple_parser.MOperator(args,
'',
'minus')
2913 jacobian[key] = new_expr
2915 solver_info.add_all_info()
2917 solver_info.add_variable_links()
2918 solver_info.add_linear_ode_update_equations()
2919 DEBUG(
'translate',
"+++ Parsed and incorporated Maple output")
2921 options.include_dt_in_tables =
False
2932 pe.parteval(doc, solver_info, lut)
2933 DEBUG(
'translate',
"+++ Done PE")
2937 lut.analyse_model(doc, solver_info)
2938 DEBUG(
'translate',
"+++ Done LT analysis")
2940 if options.rush_larsen:
2942 rl.analyse_model(doc)
2943 DEBUG(
'translate',
"+++ Done Rush-Larsen analysis")
2945 if options.translate:
2947 initargs = {
'add_timestamp':
not options.no_timestamp,
2949 transargs = {
'v_variable': config.V_variable}
2950 transargs[
'row_lookup_method'] = options.row_lookup_method
2951 transargs[
'lt_index_uses_floor'] = options.lt_index_uses_floor
2952 transargs[
'constrain_table_indices'] = options.constrain_table_indices
2953 """if issubclass(translator_klass, CellMLToMapleTranslator):
2954 initargs['omit_constants'] = options.omit_constants
2955 initargs[
'compute_full_jacobian'] = options.compute_full_jacobian
2957 from CellMLToNektarTranslator
import CellMLToNektarTranslator
2958 if issubclass(translator_klass, CellMLToNektarTranslator):
2959 solver_info.add_membrane_ionic_current()
2960 transargs[
'use_chaste_stimulus'] = options.use_chaste_stimulus
2961 transargs[
'separate_lut_class'] = options.separate_lut_class
2962 transargs[
'convert_interfaces'] = options.convert_interfaces
2963 transargs[
'use_modifiers'] = options.use_modifiers
2964 transargs[
'use_data_clamp'] = options.use_data_clamp
2965 transargs[
'dynamically_loadable'] = options.dynamically_loadable
2966 transargs[
'use_protocol'] = bool(options.protocol)
2967 t = translator_klass(**initargs)
2968 t.translate(doc, model_file, output_filename, class_name=class_name, **transargs)
2969 cellml_metadata.remove_model(doc.model)
2974 doc.xml_insert_before(doc.model, comment)
2977 doc.xml(indent=
u'yes', stream=stream)
2981 DEBUG(
'translate',
"+++ Done translation")
def output_lut_indices(self)
def output_piecewise(self, expr, paren)
def output_file_name(self, model_filename)
def output_lhs(self, expr)
def close_paren(self, paren)
def output_expr(self, expr, paren)
def output_lut_index_declarations(self, idx)
def error(self, lines, xml=None)
def __init__(self, add_timestamp=True, options=None)
def output_variable(self, ci_elt, ode=False)
def output_root(self, expr, paren)
def get_captured_output(self)
def translate(self, doc, model_filename, output_filename=None, subsidiary_file_name=None, class_name=None, v_variable=None, continuation=None, lookup_method_prefix='', row_lookup_method=False, lt_index_uses_floor=True, constrain_table_indices=False)
def output_top_boilerplate(self)
def lut_parameters(self, key)
def output_mathematics(self)
def final_configuration_hook(self)
def open_paren(self, paren)
def output_bottom_boilerplate(self)
_main_output_to_subsidiary
def generate_interface(doc, solver_info)
dictionary FILE_EXTENSIONS
def scan_for_lookup_tables(self)
Lookup table methods #.
def output_equations(self, nodeset)
def output_log(self, expr, paren)
def is_lookup_table(self, expr)
def output_number(self, expr)
def set_indent(self, level=None, offset=None)
def output_comment(self, *args, **kwargs)
def contained_table_indices(self, node)
def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[])
Dependency related methods #.
def output_single_lookup(self, tidx, tname, result)
def code_name(self, var, ode=False, prefix=None)
string TYPE_CONST_UNSIGNED
def output_function(self, func_name, args, paren, reciprocal=False)
def eval_number(self, expr)
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 subsidiary_file_name(self, output_filename)
def output_minus(self, expr, paren)
def lut_size_calculation(self, min, max, step)
def output_lut_row_lookup_methods(self)
def lut_access_code(self, table_index, table_name, i)
def open_block(self, **kwargs)
def lut_factor(self, idx, include_comma=False, include_type=False)
def output_unary_operator(self, operator, operand, paren)
def output_lut_row_lookup_memory(self)
bool USES_SUBSIDIARY_FILE
def output_lut_generation(self, only_index=None)
def output_nary_operator(self, operator, operands, paren)
def varobj(self, varname)
def close_block(self, blank_line=True, **kwargs)
def register(cls, subclass, name)
def output_table_lookup(self, expr, paren)
def output_lut_deletion(self, only_index=None)
def send_main_output_to_subsidiary(self, to_subsidiary=True)
def var_display_name(self, var)
string STMT_END
Various language tokens #.
string DOXYGEN_COMMENT_START
def output_apply(self, expr, paren)
def output_binary_operator(self, operator, operands, paren, expr)
def output_table_index_generation(self, time_name, nodeset=set())
def output_lut_declarations(self)
def finalize_config(self)
def _parse_lookup_tables(self, lookup_tables)
def _parse_Vm(self, vm_elt)
def _find_variable(self, defn, pe_done=False)
def find_current_vars(self)
def _set_lut_defaults(self, lut_dict)
def find_lookup_variables(self)
def _parse_var(self, elt, name)
def _process_ci_elts(self, elt, func, **kwargs)
def __init__(self, doc, options=None)
def _parse_Cm(self, cm_elt)
def _parse_currents(self, currents)
def find_membrane_capacitance(self)
def _create_var_def(self, content, defn_type)
def validate_metadata(self, assume_valid=False)
def annotate_currents_for_pe(self)
def _check_var_def(self, var_elt, var_desc)
def _find_transmembrane_currents_from_voltage_ode(self)
def _find_var(self, oxmeta_name, definitions)
def expose_variables(self)
def annotate_metadata_for_pe(self)
def find_transmembrane_potential(self)
def read_configuration_file(self, config_file)
def _process_mathematics(self, func)
def create_dt(self, modifier, comp, units)
def _fix_jac_var_name(self, vname)
def add_linearised_odes(self)
def add_linear_ode_update_equations(self)
def get_modifiable_mathematics(self)
def add_dt_reference(self)
def _get_variable(self, varname)
def do_binding_time_analysis(self)
def __init__(self, model, force=False)
def add_membrane_ionic_current(self)
def _add_variable_links(self, elt)
def add_variable_links(self)
def add_transmembrane_potential_name(self)
def has_modifiable_mathematics(self)
def _get_special_variable(self, varname, ptype=VarTypes.Unknown)
def get_linearised_odes(self)
def use_canonical_variable_names(self)
def add_jacobian_matrix(self)
def _check_state_var_units_conversions(self)
def _get_special_component(self)
def get_units_by_name(self, uname)
def amara_parse_cellml(source, uri=None, prefixes=None)
def description(self, force=False, cellml=False)
def get_options(args, default_options=None)
def version_comment(note_time=True)
def load_model(model_file, options)
def open_output_stream(fname)
def amara_parse(source, uri=None, rules=None, binderobj=None, prefixes=None)
def LOG(facility, level, *args)
def call_if(tf, callable, *args, **kwargs)
def close_output_stream(stream)
def DEBUG(facility, *args)
def validate(self, stream)
std::vector< double > d(NPUPPER *NPUPPER)