Nektar++
translators.py
Go to the documentation of this file.
1# We want 1/2==0.5
2from __future__ import division
3
4"""Copyright (c) 2005-2016, University of Oxford.
5All rights reserved.
6
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.
10
11This file is part of Chaste.
12
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.
23
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.
34"""
35
36"""
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).
40"""
41
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)."
48
49# Common CellML processing stuff, all of these are in the /pycml folder
50import pycml
51from pycml import * # Put contents in the local namespace as well
52import optimize
53import processors
54import validator
55
56
57#this sets the version
58__version__ = "$Revision: 25950 $"[11:-2]
59
60
61#this function generates a version comment
62def version_comment(note_time=True):
63 """Generate a version comment, with optional time info."""
64 if note_time:
65 t = '\non ' + time.asctime()
66 else:
67 t = ''
68 text = """Processed by pycml - CellML Tools in Python
69 (translators: %s, pycml: %s, optimize: %s)%s""" % (
70 __version__, pycml.__version__, optimize.__version__, t)
71 return text
72
73#some debugging function?
74def debugexpr(e):
75 "For debugging."
76 v = None
77 if isinstance(e, cellml_variable):
78 v = e
79 elif isinstance(e, mathml_apply):
80 v = e.assigned_variable()
81 if v:
82 r = (v==e, v.name, v.get_usage_count())
83 else:
84 r = (False, '', -1)
85 return r
86
87
88class TranslationError(RuntimeError):
89 """Error thrown if CellML translation fails."""
90 pass
91
92class ConfigurationError(ValueError):
93 """Error thrown if configuration file is invalid."""
94 pass
95
96
97
98class CellMLTranslator(object):
99 """
100 Base class for translators from CellML to programming languages.
101
102 Provides various methods & attributes that can be overridden to
103 achieve the desired output language and style.
104
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.
108 """
109
110 translators = {} #creates an empty dictionary, into which the classes are registered (the registering is done in modified_translate)
111 class NameAlreadyRegistered(ValueError):
112 pass
113
114
115#this is the function used by modified_translate to register each module into the translators dictionary
116 @classmethod
117 def register(cls, subclass, name):
118 """Register a new translator subclass.
119
120 Registers the subclass `subclass' with name `name' in the
121 translators class attribute of CellMLTranslator. If the name
122 given is already in use, raises NameAlreadyRegistered.
123 """
124 if name in cls.translators:
125 raise cls.NameAlreadyRegistered(name)
126 cls.translators[name] = subclass
127 return
128
129
130 @staticmethod
131 def generate_interface(doc, solver_info):
132 """Generate an interface component connecting the model to whatever will use it.
133
134 Stub method that subclasses can override to implement this functionality.
135 """
136 pass
137
138 ###########################
139 # Various language tokens #
140 ###########################
141 STMT_END = ';' # End of statement
142 EQ_ASSIGN = ' = ' # Assignment operator
143 COMMENT_START = '// ' # Start of a 1 line comment
144 DOXYGEN_COMMENT_START = '//! ' # Start of a 1 line Doxygen comment
145
146 # Variable types
147 TYPE_DOUBLE = 'NekDouble '
148 TYPE_VOID = 'void '
149 TYPE_CONST_DOUBLE = 'const NekDouble '
150 TYPE_CONST_UNSIGNED = 'const unsigned '
151
152 # Special constants
153 TRUE = 'true'
154 FALSE = 'false'
155 PI = 'M_PI'
156 E = 'M_E'
157 NOT_A_NUMBER = 'NAN' # GNU extension, but fairly common
158
159 # Whether the target language uses a subsidiary file, such as
160 # a header file in C/C++
161 USES_SUBSIDIARY_FILE = False
162 # Mapping from primary file extension to subsidiary file extension
163 FILE_EXTENSIONS = {'cpp': 'h',
164 'c': 'h',
165 'cxx': 'hxx'}
166
167 def __init__(self, add_timestamp=True, options=None):
168 """Create a translator."""
169 self.options = options
170 # Initially output should not be indented
172 # Character to indent with
173 self.indent_char = ' '
174 # No. of occurrences of indent_char per indent_level
176 # Whether to use lookup tables where possible
178 # Whether to add a timestamp comment to generated files
179 self.add_timestamp = add_timestamp
180 # Main output goes to the main file by default
182
183 def error(self, lines, xml=None):
184 """Raise a translation error.
185
186 lines is a list of strings describing what went wrong.
187 A TranslationError with that message will be raised.
188
189 If xml is given, it should be an element, which will be
190 pretty-printed and included in the error.
191 """
192 if xml is not None:
193 lines.extend(xml.xml(indent = u'yes',
194 omitXmlDeclaration = u'yes').split('\n'))
195 raise TranslationError('\n'.join(lines))
196
197 @property
198 def config(self):
199 """Get the current document's configuration store."""
200 return getattr(self.doc, '_cml_config', None)
201
202 def translate(self, doc, model_filename, output_filename=None,
203 subsidiary_file_name=None,
204 class_name=None, v_variable=None,
205 continuation=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.
209
210 doc should be an instance of cellml_model representing a
211 valid CellML model, such as might be produced from a call
212 to
213 >>> valid, doc = validator.CellMLValidator().validate(
214 ... model_filename, True)
215
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.
220
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.
224
225 The variable representing the transmembrane potential should
226 be passed in using the v_variable argument.
227
228 By default this method will perform some setup and then call
230 self.output_mathematics()
232 To alter this, pass a callable as the continuation parameter;
233 this will then be called instead.
234
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.
241
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.
245
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.
249 """
250 self.doc = doc
251 self.model = doc.model
252 # Name of the class that will represent this model
253 if class_name is None:
254 self.class_name = u'CML_' + doc.model.name.replace('-', '_')
255 else:
256 self.class_name = class_name
257 # Figure out the free & state vars in this model
258 self.free_vars = doc.model.find_free_vars()
259 self.state_vars = doc.model.find_state_vars()
260 if len(self.free_vars) > 1:
261 self.error(["Model has more than one free variable; exiting.",
262 "Free vars:" + str(self.free_vars)])
263 if len(self.free_vars) == 0:
264 if self.model.get_option('protocol'):
265 # We may be dealing with an algebraic model; check for an Unknown variable
266 for var in self.model.get_all_variables():
267 if var.get_type() == VarTypes.Unknown:
268 self.free_vars.append(var)
269 if len(self.free_vars) != 1:
270 self.error(["Model has no free variable; exiting."])
271 # If only a single component, don't add it to variable names
272 self.single_component = (len(getattr(self.model, u'component', [])) == 1)
273 # Find the (index of the) transmembrane potential
274 self.v_variable = v_variable
275 if self.v_variable:
276 self.v_variable_name = (v_variable.component.name, v_variable.name)
277 else:
278 self.v_variable = None
279 for i, var in enumerate(self.state_vars):
280 if var is v_variable:
281 self.v_index = i
282 break
283 else:
284 self.v_index = -1
285 # Check to see if we're using lookup tables
286 self.lookup_method_prefix = lookup_method_prefix
287 self.row_lookup_method = row_lookup_method
288 self.lt_index_uses_floor = lt_index_uses_floor
289 self.constrain_table_indices = constrain_table_indices
291 if not doc.lookup_tables:
292 # No tables found
293 self.use_lookup_tables = False
294
295 # Extra configuration hook
297
298 # Open the output file(s)
299 if output_filename is None:
300 output_filename = self.output_file_name(model_filename)
302 output_filename, self.subsidiary_filename = self.subsidiary_file_name(output_filename)
303 self.output_filename = output_filename
304 self.out = open_output_stream(output_filename)
307
308 # Translate
309 if continuation:
310 continuation()
311 else:
313 self.output_mathematics()
318 return
319
321 """A hook for subclasses to do some final configuration."""
322 return
323
324 def output_file_name(self, model_filename):
325 """Generate a name for our output file, based on the input file."""
326 return os.path.splitext(model_filename)[0] + '.cpp'
327
328 def subsidiary_file_name(self, output_filename):
329 """Generate a name for the subsidiary output file, based on the main one.
330
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.
334 """
335 base, ext = os.path.splitext(output_filename)
336 ext = ext[1:] # Remove the '.'
337 try:
338 new_ext = self.FILE_EXTENSIONS[ext]
339 swap = False
340 except KeyError:
341 swap = True
342 for key, val in self.FILE_EXTENSIONS.iteritems():
343 if val == ext:
344 new_ext = key
345 break
346 else:
347 # Turn off usage of subsidiary file
349 return output_filename, None
350 subsidiary_filename = base + '.' + new_ext
351 if swap:
352 output_filename, subsidiary_filename = subsidiary_filename, output_filename
353 return output_filename, subsidiary_filename
354
355 def send_main_output_to_subsidiary(self, to_subsidiary=True):
356 """Set subsequent main-file writes to go to the subsidiary file instead.
357
358 Supplying a False argument reverts this setting.
359
360 Has no effect if not using a subsidiary file.
361 """
362 self._main_output_to_subsidiary = to_subsidiary
363
364 def writeln(self, *args, **kwargs):
365 """Write a line to our output file.
366
367 Takes any number of strings as input, and writes them out to file.
368
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.
373
374 If nl is set to False then a newline character will not be
375 appended to the output.
376
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.
380 """
381 if kwargs.get('subsidiary', False) or self._main_output_to_subsidiary:
383 self.error('Tried to write to non-existent subsidiary file')
384 else:
385 target = self.out2
386 else:
387 target = self.out
388 indent = kwargs.get('indent', True)
389 nl = kwargs.get('nl', True)
390 if indent:
391 level = kwargs.get('indent_level', self.indent_level)
392 level += kwargs.get('indent_offset', 0)
393 target.write(self.indent_char * self.indent_factor * level)
394 target.write(''.join(map(str, args)))
395 if nl:
396 target.write('\n')
397
398 def write(self, *args):
399 """Write to our output file.
400
401 This variant does not indent the output, or add a newline.
402 """
403 self.writeln(indent=False, nl=False, *args)
404
405 def capture_output(self):
406 """Make subsequent output operations write to a string buffer."""
407 self._original_out = self.out
408 self.out = StringIO()
409
411 """Stop capturing output, and return what was captured as a string."""
412 output = self.out.getvalue()
413 self.out = self._original_out
414 return output
415
416 def output_comment(self, *args, **kwargs):
417 """Output a (multi-line) string as a comment."""
418 start = kwargs.get('start', self.COMMENT_START)
419 if kwargs.get('pad', False):
420 start = ' ' + start
421 comment = ''.join(map(str, args))
422 lines = comment.split('\n')
423 for line in lines:
424 self.writeln(start, line, **kwargs)
425
426 def output_doxygen(self, *args, **kwargs):
427 """Output a (multi-line) string as a Doxygen comment."""
428 kwargs['start'] = self.DOXYGEN_COMMENT_START
429 self.output_comment(*args, **kwargs)
430
431 def set_indent(self, level=None, offset=None):
432 """Set the indentation level for subsequent writes.
433
434 If offset is given, adjust the level by that amount, otherwise
435 set it to an absolute value.
436 """
437 if offset is not None:
438 self.indent_level += offset
439 else:
440 self.indent_level = level
441
442 def code_name(self, var, ode=False, prefix=None):
443 """
444 Return the full name of var in a form suitable for inclusion in a
445 source file.
446
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.
451 """
452 if prefix is None:
453 prefix = ['var_', 'd_dt_'][ode]
454 if ode:
455 var = var.get_source_variable(recurse=True)
456 name = prefix + var.fullname(cellml=True)
457 return name
458
459 def varobj(self, varname):
460 """Return the variable object that has code_name varname."""
461 return cellml_variable.get_variable_object(self.model, varname)
462
463 def var_display_name(self, var):
464 """Return a display name for the given variable.
465
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.
469 """
470 if var.oxmeta_name:
471 name = var.oxmeta_name
472 else:
473 for uri in var.get_rdf_annotations(('bqbiol:is', NSS['bqbiol'])):
474 if '#' in uri:
475 name = uri[1 + uri.rfind('#'):]
476 break
477 else:
478 if hasattr(var, u'id') and var.id:
479 name = var.id
480 else:
481 name = var.name
482 iface = getattr(self.model, 'interface_component_name', '#N/A#')
483 if name.startswith(iface):
484 name = name[len(iface)+2:]
485 return name
486
487 @property
488 def include_guard(self):
489 """
490 Get the include guard (for C/C++ output) for this cell model,
491 based on the class name.
492 """
493 return self.class_name.upper() + '_HPP_'
494
496 """Output top boilerplate."""
497 self.writeln('#ifndef _', self.include_guard, '_')
498 self.writeln('#define _', self.include_guard, '_\n')
499 self.output_comment('Model: ', self.model.name)
501 self.writeln()
502 self.writeln('#include <cmath>')
503 self.writeln('#include "AbstractOdeSystem.hpp"')
504 self.writeln('#include "Exception.hpp"')
505 self.writeln('#include "AbstractStimulusFunction.hpp"\n')
506 self.writeln('class ', self.class_name, ' : public AbstractOdeSystem')
507 self.writeln('{')
508 self.writeln('private:')
509 self.writeln('AbstractStimulusFunction *mpStimulus;\n',
510 indent_offset=1)
511 self.writeln('public:')
512 self.set_indent(1)
513 self.writeln('const static unsigned _NB_OF_STATE_VARIABLES_ = ',
514 str(len(self.state_vars)), ';\n')
515 self.writeln('//', ('-'*66))
516 self.writeln('// Methods')
517 self.writeln('//', ('-'*66), '\n')
518 # Constructor
519 self.writeln('', self.class_name,
520 '(AbstractStimulusFunction *stim)')
521 self.writeln(' : AbstractOdeSystem(_NB_OF_STATE_VARIABLES_, ',
522 self.v_index, ')')
523 self.open_block()
524 self.writeln('mpStimulus = stim;\n')
525 for var in self.state_vars:
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)
529 if init_val is None:
530 init_comm = ' // Value not given in model'
531 # Don't want compiler error, but shouldn't be a real number
532 init_val = self.NOT_A_NUMBER
533 else:
534 init_comm = ''
535 self.writeln('mInitialConditions.push_back(', init_val, ');',
536 init_comm, '\n')
538 self.close_block()
539 # Destructor
540 self.writeln('~', self.class_name, '(void)')
541 self.open_block()
543 self.close_block()
544 # Lookup table declarations & methods
545 if self.use_lookup_tables:
547 self.output_lut_methods()
548 # Evaluation function
549 self.writeln('void EvaluateYDerivatives (')
550 self.writeln(' double ', self.code_name(self.free_vars[0]), ',')
551 self.writeln(' const std::vector<double> &rY,')
552 self.writeln(' std::vector<double> &rDY)')
553 self.open_block()
554 self.writeln('// Inputs:')
555 self.writeln('// Time units: ', self.free_vars[0].units)
556 for i, var in enumerate(self.state_vars):
557 self.writeln('double ', self.code_name(var),
558 ' = rY[', str(i), '];')
559 self.writeln('// Units: ', var.units, '; Initial value: ',
560 getattr(var, u'initial_value', 'Unknown'))
561 self.writeln()
562 if self.use_lookup_tables:
564 return
565
567 """Output the mathematics in this model."""
568 self.writeln(self.COMMENT_START, 'Mathematics')
569 for expr in self.model.get_assignments():
570 # Check this expression is actually used; don't output if not
571 var = None
572 if isinstance(expr, mathml_apply) and expr.is_assignment():
573 var = expr.assigned_variable()
574 elif isinstance(expr, cellml_variable):
575 var = expr
576 if not (var and var.get_usage_count() == 0):
577 self.output_assignment(expr)
578 return
579
581 """Output bottom boilerplate"""
582 self.writeln('\n')
583 for i, var in enumerate(self.state_vars):
584 self.writeln('rDY[', str(i), '] = ', self.code_name(var, True),
585 ';')
586 self.close_block()
587 self.set_indent(offset=-1)
588 self.writeln('};\n')
589 self.writeln('#endif')
590 return
591
592 def output_assignment(self, expr):
593 """Output an assignment expression."""
594 if isinstance(expr, cellml_variable):
595 # self.writeln('Test')
596 # This may be the assignment of a mapped variable, or a constant
597 t = expr.get_type()
598 if t == VarTypes.Mapped:
599 # self.writeln(self.code_name(expr))
600 self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(expr),
601 self.EQ_ASSIGN,
602 self.code_name(expr.get_source_variable()),
603 self.STMT_END, nl=False)
604 self.output_comment(expr.units, indent=False, pad=True)
605 elif t == VarTypes.Constant:
606 # self.writeln('Test')
607 # self.writeln(self.code_name(expr))
608
609
610 # setting various stim-parameters to zero, since Nektar uses its own stimulus
611 if 'stim_amplitude' in str(self.code_name(expr)):
612 self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(expr),
613 self.EQ_ASSIGN, nl=False)
614 self.output_number(0)
615 self.writeln(self.STMT_END, indent=False, nl=False)
616 self.output_comment(expr.units, indent=False, pad=True)
617 else:
618 self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(expr),
619 self.EQ_ASSIGN, nl=False)
620 self.output_number(expr.initial_value)
621 self.writeln(self.STMT_END, indent=False, nl=False)
622 self.output_comment(expr.units, indent=False, pad=True)
623 else:
624 # This is a mathematical expression
625 self.writeln(self.TYPE_CONST_DOUBLE, nl=False)
626 opers = expr.operands()
627 self.output_lhs(opers.next())
628 self.write(self.EQ_ASSIGN)
629 self.output_expr(opers.next(), False)
630 self.writeln(self.STMT_END, indent=False, nl=False)
631 #1365: add a comment with the LHS units
632 self.output_comment(expr._get_element_units(expr.eq.lhs, return_set=False).description(),
633 indent=False, pad=True)
634
635 def output_lhs(self, expr):
636 """Output the left hand side of an assignment expression."""
637 if expr.localName == 'ci':
638 self.output_variable(expr)
639 elif expr.operator().localName == 'diff':
640 self.write(self.code_name(expr.operator().dependent_variable, ode=True))
641
642 def output_variable(self, ci_elt, ode=False):
643 """Output a ci element, i.e. a variable lookup."""
644 self.write(self.code_name(ci_elt.variable, ode=ode))
645
646 def output_expr(self, expr, paren):
647 """Output the expression expr.
648
649 If paren is True then the context has requested parentheses around the
650 output; if expr requires them then they will be added.
651 """
652 if self.use_lookup_tables and self.is_lookup_table(expr):
653 self.output_table_lookup(expr, paren)
654 elif isinstance(expr, mathml_apply):
655 self.output_apply(expr, paren)
656 elif isinstance(expr, mathml_piecewise):
657 self.output_piecewise(expr, paren)
658 elif isinstance(expr, mathml_ci):
659 self.output_variable(expr)
660 elif expr.localName == u'cn':
661 self.output_number(expr)
662 elif expr.localName == u'degree':
663 # <degree> is just a wrapper around an expression
664 self.output_expr(child_i(expr, 1), paren)
665 elif expr.localName == u'logbase':
666 # <logbase> is just a wrapper around an expression
667 self.output_expr(child_i(expr, 1), paren)
668 elif expr.localName == u'true':
669 self.write(self.TRUE)
670 elif expr.localName == u'false':
671 self.write(self.FALSE)
672 elif expr.localName == u'pi':
673 self.write(self.PI)
674 elif expr.localName == u'exponentiale':
675 self.write(self.E)
676 else:
677 self.error(["Unsupported expression element " + expr.localName],
678 xml=expr)
679
680 def output_number(self, expr):
681 """Output the plain number expr.
682
683 We make all constants parse as doubles to avoid problems with
684 integer division or numbers too large for the int type.
685
686 Negative numbers will be prefixed by a space to avoid unwanted
687 decrement operations.
688 """
689 n = self.eval_number(expr)
690 num = "%.17g" % n
691 if num[0] == '-':
692 num = ' ' + num
693 if not '.' in num and not 'e' in num:
694 num = num + '.0'
695 self.write(num)
696
697 def eval_number(self, expr):
698 """Evaluate a number.
699
700 If a (unicode) string, convert to float.
701 If a cn element, call its evaluate method.
702 """
703 if isinstance(expr, mathml_cn):
704 return expr.evaluate()
705 else:
706 return float(unicode(expr))
707
708 # Map from operator element names to C++ function names,
709 # where the translation is straightforward.
710 function_map = {'power': 'pow', 'abs': 'fabs', 'ln': 'log', 'exp': 'exp',
711 'floor': 'floor', 'ceiling': 'ceil',
712 'factorial': 'factorial', # Needs external definition
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'}
720 # Inverse reciprocal trig functions; these are represented by
721 # key(x) = function_map[val](1/x)
722 recip_trig = {'arcsec': 'arccos', 'arccsc': 'arcsin', 'arccot': 'arctan',
723 'arcsech': 'arccosh', 'arccsch': 'arcsinh', 'arccoth': 'arctanh'}
724 # Operators
725 nary_ops = {'plus': '+', 'times': '*',
726 'and': '&&', 'or': '||'}
727 binary_ops = {'divide': '/',
728 'xor': '!=', 'eq': '==', 'neq': '!=',
729 'geq': '>=','leq': '<=','gt': '>','lt': '<'}
730
731 def output_apply(self, expr, paren):
732 """Output an <apply> expression.
733
734 paren is True if the context has requested parentheses.
735 """
736 op = expr.operator()
737 if op.localName in self.function_map:
738 self.output_function(self.function_map[op.localName],
739 expr.operands(), paren)
740 elif op.localName in self.recip_trig:
741 self.output_function(self.function_map[self.recip_trig[op.localName]],
742 expr.operands(), paren, reciprocal=True)
743 elif op.localName == u'root':
744 self.output_root(expr, paren)
745 elif op.localName == u'log':
746 self.output_log(expr, paren)
747 elif op.localName in self.nary_ops:
748 self.output_nary_operator(self.nary_ops[op.localName],
749 expr.operands(), paren)
750 elif op.localName in self.binary_ops:
751 self.output_binary_operator(self.binary_ops[op.localName],
752 expr.operands(), paren, expr)
753 elif op.localName == u'minus':
754 self.output_minus(expr, paren)
755 elif op.localName == u'diff':
756 # ODE occuring on the RHS
757 self.write(self.code_name(op.dependent_variable, ode=True))
758 else:
759 # Unrecognised operator
760 self.error(["Unsupported operator element " + str(op.localName)], xml=expr)
761
762 def output_function(self, func_name, args, paren, reciprocal=False):
763 """Output a function call with name func_name and arguments args.
764
765 Parentheses are not required so paren is ignored.
766 If reciprocal is True then pass the reciprocal of each arg to
767 func_name.
768 """
769 self.write(func_name + '(')
770 comma = False
771 for arg in args:
772 if comma: self.write(', ')
773 else: comma = True
774 if reciprocal:
775 self.write('1/')
776 self.output_expr(arg, True)
777 else:
778 self.output_expr(arg, False)
779 self.write(')')
780
781 def output_nary_operator(self, operator, operands, paren):
782 """Output an n-ary operator (using infix notation).
783
784 If paren is True, enclose the output in parentheses.
785 """
786 # TODO: Optimise - to use expm1(x) for computing exp(x)-1
787 self.open_paren(paren)
788 op = False
789 for operand in operands:
790 if op: self.write(' ' + operator + ' ')
791 else: op = True
792 self.output_expr(operand, True)
793 self.close_paren(paren)
794
795 def output_unary_operator(self, operator, operand, paren):
796 """Output a unary operator (using prefix notation)."""
797 self.open_paren(paren)
798 self.write(operator)
799 self.output_expr(operand, True)
800 self.close_paren(paren)
801
802 def output_binary_operator(self, operator, operands, paren, expr):
803 """Output a binary operator.
804
805 As output_nary_operator, but checks that len(list(operands)) == 2.
806 """
807 operands = list(operands)
808 if len(operands) != 2:
809 self.error(["Binary operator" + operator +
810 "does not have 2 operands."], xml=expr)
811 self.output_nary_operator(operator, operands, paren)
812
813 special_roots = {2: 'sqrt', 3: 'cbrt'}
814 def output_root(self, expr, paren):
815 """Output a root taken to some degree.
816
817 If a degree qualifier element is not provided, uses default 2.
818 """
819 if hasattr(expr, u'degree'):
820 # A degree is given. Compute x^(1/b)
821 # TODO: Optimise for when b==2 (sqrt) or b==3 (cbrt)
822 # Try to evaluate expr.degree, and if the result is a key
823 # of self.special_roots, use the value as the function to call.
824 self.write('pow(')
825 self.output_expr(expr.operands().next(), False)
826 self.write(', 1/')
827 self.output_expr(expr.degree, True)
828 self.write(')')
829 else:
830 # Compute square root
831 self.output_function('sqrt', expr.operands(), paren)
832
833 def output_log(self, expr, paren):
834 """Output a logarithm to the given base, which defaults to base 10."""
835 if hasattr(expr, u'logbase'):
836 # A base is provided. Use the identity log_b(x) = log(x)/log(b)
837 # TODO: Optimise for log2(x)
838 self.open_paren(paren)
839 self.output_function('log', expr.operands(), paren)
840 self.write('/log(')
841 self.output_expr(expr.logbase, False)
842 self.write(')')
843 self.close_paren(paren)
844 else:
845 # Use base 10
846 self.output_function('log10', expr.operands(), paren)
847
848 def output_minus(self, expr, paren):
849 """Output either a unary or binary minus.
850
851 Which is chosen depends on the number of operands.
852 """
853 operands = list(expr.operands())
854 if len(operands) == 1:
855 self.output_unary_operator('-', operands[0], paren)
856 else:
857 self.output_binary_operator('-', operands, paren, expr)
858
859 def output_piecewise(self, expr, paren):
860 """Output the piecewise expression expr.
861
862 We use a cascading ternary if expression for simplicity.
863 """
864 self.open_paren(paren)
865 for piece in getattr(expr, u'piece', []):
866 self.output_expr(child_i(piece, 2), True) # Condition
867 self.write(' ? ')
868 self.output_expr(child_i(piece, 1), True) # Result
869 self.write(' : ')
870 if hasattr(expr, u'otherwise'):
871 self.output_expr(child_i(expr.otherwise, 1), True) # Default case
872 else:
873 self.write(self.NOT_A_NUMBER)
874 self.close_paren(paren)
875
876 def open_paren(self, paren):
877 if paren: self.write('(')
878 def close_paren(self, paren):
879 if paren: self.write(')')
880
881 def open_block(self, **kwargs):
882 """Open a new code block and increase indent."""
883 self.writeln('{', **kwargs)
884 self.set_indent(offset=1)
885 def close_block(self, blank_line=True, **kwargs):
886 """Close a code block and decrease indent."""
887 self.set_indent(offset=-1)
888 self.writeln('}', **kwargs)
889 if blank_line:
890 self.writeln(**kwargs)
891 return
892
893 ##############################
894 # Dependency related methods #
895 ##############################
896
897 # These methods allow us to calculate which equations must be
898 # output in order to compute a given set of quantities.
899 def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[]):
900 """Method moved to cellml_model."""
901
902 # for var in nodes:
903 # self.writeln(var)
904 # self.writeln()
905
906 # for var in self.model.calculate_extended_dependencies(nodes, prune, prune_deps):
907 # self.writeln(var)
908 # self.writeln()
909
910
911 return self.model.calculate_extended_dependencies(nodes, prune, prune_deps)
912
913 def output_equations(self, nodeset):
914 """Output the mathematics described by nodeset.
915
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.
919
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.
923 """
924 for expr in (e for e in self.model.get_assignments() if e in nodeset):
925 self.output_assignment(expr)
926 return
927
928 def _vars_in(self, expr):
929 """Return a set of variable objects used in the given expression.
930
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.
934 """
935 res = set()
936 if self.use_lookup_tables and isinstance(expr, mathml) and self.is_lookup_table(expr):
937 key_var = self.varobj(expr.getAttributeNS(NSS['lut'], u'var'))
938 key_var = key_var.get_source_variable(recurse=True)
939 res.add(key_var)
940 elif isinstance(expr, mathml_ci):
941 varobj = getattr(expr, '_cml_variable', None)
942 if not varobj:
943 varname = unicode(expr)
944 varobj = self.varobj(varname.strip())
945 if varobj:
946 res.add(varobj)
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:
953 res.update(self._vars_in(child))
954 return res
955
956
957 ########################
958 # Lookup table methods #
959 ########################
960
961 # Lookup tables should be done in a cache- and memory-
962 # efficient manner.
963 #
964 # Cache: Have one block of memory allocated for all tables with a
965 # given index variable, such that entries are found at i*N+j,
966 # where N is the no. of tables in the block, i is the index into a
967 # table, and j is the table to read. Change how lookups are done,
968 # such that the lookup method is called once and returns a pointer
969 # to the (i*N)'th entry. Places where we now call the method then
970 # index this pointer by j.
971 # The 'one block' part is done by default.
972 # The better lookup method is activated by --row-lookup-method.
973 #
974 # Memory: Extract the lookup tables into a separate class (in the
975 # same .cpp file though). This can then be made a singleton class
976 # in a multi-cellular context.
977 # Chaste code generation has the option to do this, enabled by
978 # default. Use --no-separate-lut-class to disable.
979
981 """Search for lookup tables used in this document.
982
983 Store a list of suitable expressions on the document root.
984 Generate a dictionary mapping tables to their index variables.
985 """
986 doc = self.doc
987 # Get list of suitable expressions
988 doc.lookup_tables = doc.xml_xpath(u"//*[@lut:possible='yes']")
989 doc.lookup_tables.sort(cmp=element_path_cmp)
990 # Map table keys (min, max, step, var) to an index variable
991 doc.lookup_table_indexes = {}
992 # Count the no. of lookup tables with each index variable
993 doc.lookup_tables_num_per_index = {}
994 if not doc.lookup_tables:
995 # If no suitable expressions, we're done
996 return
997 # Search for table index variables already assigned
998 table_indexes = [int(getattr(expr, u'table_index', -1))
999 for expr in doc.lookup_tables]
1000 tidx = max(table_indexes) + 1
1001 # Search for table names already assigned
1002 table_numbers = {}
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))
1007 # Now assign new names, and construct mapping from tables to index variables
1008 for expr in doc.lookup_tables:
1009 # Get a suitable table index variable
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 # Table index variables shouldn't be const, in case we constrain to table bounds
1016 if hasattr(expr, u'table_index'):
1017 doc.lookup_table_indexes[key] = expr.table_index
1018 else:
1019 doc.lookup_table_indexes[key] = unicode(tidx)
1020 tidx += 1
1021 expr.xml_set_attribute((u'lut:table_index', NSS['lut']),
1022 doc.lookup_table_indexes[key])
1023 # Get a table name, unique over all tables with this index variable
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
1028 # Re-number table indices so they are contiguous starting from 0.
1029 table_index_map = {}
1030 table_name_map = {}
1031 tidx = 0
1032 for key in sorted(doc.lookup_table_indexes.keys()):
1033 idx = unicode(tidx)
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
1038 tidx += 1
1039 # Make sure each lookup table is only listed once in doc.lookup_tables,
1040 # so we don't get 2 tables for the same expression!
1041 # Also re-number table names so they are contiguous starting from 0 for each table index.
1042 candidates = doc.lookup_tables[:]
1043 doc.lookup_tables = []
1044 listed = set()
1045 for expr in candidates:
1046 tid = (expr.table_index, expr.table_name)
1047 if not tid in listed:
1048 listed.add(tid)
1049 doc.lookup_tables.append(expr)
1050 # Renumber
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]
1054 # Increment count for this index variable
1055 doc.lookup_tables_num_per_index[expr.table_index] += 1
1056 else:
1057 # Just renumber to match the new id for this expression
1058 expr.table_index = table_index_map[expr.table_index]
1059 expr.table_name = table_name_map[expr.table_index][expr.table_name]
1060 return
1061
1062 def lut_access_code(self, table_index, table_name, i):
1063 """Get the code for accessing the i'th element of the given table.
1064 """
1065 return '_lookup_table_%s[%s][%s]' % (table_index, i, table_name)
1066
1067 def lut_parameters(self, key):
1068 """Get the bounds and step size for a particular table.
1069
1070 key should be a key into self.lookup_table_indices.
1071 Returns (min, max, step, step_inverse) suitable for putting in generated code.
1072 """
1073 return key[0:3] + [unicode(1 / float(key[2]))]
1074
1075 def lut_size_calculation(self, min, max, step):
1076 """Return the equivalent of '1 + (unsigned)((max-min)/step+0.5)'."""
1077 return '1 + (unsigned)((%s-%s)/%s+0.5)' % (max, min, step)
1078
1079 def output_lut_generation(self, only_index=None):
1080 """Output code to generate lookup tables.
1081
1082 There should be a list of suitable expressions available as self.doc.lookup_tables,
1083 to save having to search the whole model.
1084
1085 If only_index is given, only generate tables using the given table index key.
1086 """
1087 # Don't use table lookups to generate the tables!
1088 self.use_lookup_tables = False
1089 # Allocate memory for tables
1090 for key, idx in self.doc.lookup_table_indexes.iteritems():
1091 if only_index is None or only_index == idx:
1092 min, max, step, _ = self.lut_parameters(key)
1093 self.writeln(self.TYPE_CONST_UNSIGNED, '_table_size_', idx, self.EQ_ASSIGN,
1094 self.lut_size_calculation(min, max, step), self.STMT_END)
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)
1097 # Generate each table in a separate loop
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:
1103 continue
1104 min, max, step, _ = self.lut_parameters(key)
1105 j = expr.table_name
1106 self.writeln('for (unsigned i=0 ; i<_table_size_', idx, '; i++)')
1107 self.open_block()
1108 self.writeln(self.TYPE_CONST_DOUBLE, self.code_name(var), self.EQ_ASSIGN, min,
1109 ' + i*', step, self.STMT_END)
1110 self.writeln(self.lut_access_code(idx, j, 'i'), self.EQ_ASSIGN, nl=False)
1111 self.output_expr(expr, False)
1112 self.writeln(self.STMT_END, indent=False)
1113 self.close_block()
1114 self.use_lookup_tables = True
1115
1116 def output_lut_deletion(self, only_index=None):
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, ')')
1121 self.open_block()
1122 self.writeln('delete[] _lookup_table_', idx, self.STMT_END)
1123 self.writeln('_lookup_table_', idx, self.EQ_ASSIGN, 'NULL', self.STMT_END)
1124 self.close_block(blank_line=False)
1125
1127 """Output declarations for the lookup tables."""
1128 self.output_comment('Lookup tables')
1129 # Allocate memory, per index variable for cache efficiency
1130 for idx in self.doc.lookup_table_indexes.itervalues():
1131 num_tables = unicode(self.doc.lookup_tables_num_per_index[idx])
1132 self.writeln(self.TYPE_DOUBLE, '(*_lookup_table_', idx, ')[', num_tables, ']', self.STMT_END)
1133 self.writeln()
1134
1136 """Output declarations the variables used to index this table."""
1137 self.writeln('unsigned _table_index_', idx, self.STMT_END)
1138 factor = self.lut_factor(idx, include_type=True)
1139 if factor:
1140 self.writeln(factor, self.STMT_END)
1141 if self.row_lookup_method:
1142 self.writeln('double* _lt_', idx, '_row', self.STMT_END)
1143
1145 """Output declarations for the lookup table indices."""
1146 self.output_comment('Lookup table indices')
1147 for idx in self.doc.lookup_table_indexes.itervalues():
1149 self.writeln()
1150
1152 """Output the methods which look up values from lookup tables."""
1153 if self.row_lookup_method:
1155 return
1156 self.output_comment('Methods to look up values from lookup tables')
1157 self.output_comment('using ', self.config.options.lookup_type)
1158 for expr in self.doc.lookup_tables:
1159 j = expr.table_name
1160 idx = expr.table_index
1161 self.writeln('inline double _lookup_', j, '(unsigned i',
1162 self.lut_factor('', include_type=True, include_comma=True), ')')
1163 self.open_block()
1164 self.output_single_lookup(idx, j, 'return ')
1165 self.close_block()
1166 self.writeln()
1167 return
1168
1169 def output_single_lookup(self, tidx, tname, result):
1170 """Write the lookup calculation for a single entry.
1171
1172 Used by output_lut_row_lookup_methods and output_lut_methods.
1173 """
1174 self.writeln(self.TYPE_CONST_DOUBLE, 'y1', self.EQ_ASSIGN,
1175 self.lut_access_code(tidx, tname, 'i'), self.STMT_END)
1176 if self.config.options.lookup_type == 'linear-interpolation':
1177 self.writeln(self.TYPE_CONST_DOUBLE, 'y2', self.EQ_ASSIGN,
1178 self.lut_access_code(tidx, tname, 'i+1'), self.STMT_END)
1179 self.writeln(result, 'y1 + (y2-y1)*', self.lut_factor(''), self.STMT_END)
1180 else:
1181 self.writeln(result, 'y1', self.STMT_END)
1182
1184 """Write methods that return a whole row of a lookup table.
1185
1186 Note: assumes that table names are numbered sequentially from 0.
1187 """
1188 self.output_comment('Row lookup methods')
1189 self.output_comment('using ', self.config.options.lookup_type)
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), ')')
1194 self.open_block()
1195 self.writeln('for (unsigned j=0; j<', num_tables, '; j++)')
1196 self.open_block()
1197 self.output_single_lookup(idx, 'j', '_lookup_table_%s_row[j] = ' % idx)
1198 self.close_block(False)
1199 self.writeln('return _lookup_table_', idx, '_row;')
1200 self.close_block()
1201 self.writeln()
1202 return
1203
1205 """Output declarations for the memory used by the row lookup methods."""
1206 self.output_comment('Row lookup methods memory')
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, '];')
1211 self.writeln()
1212 return
1213
1214 def is_lookup_table(self, expr):
1215 """Return True iff expr can be replaced by a lookup table.
1216
1217 Uses annotations from a previous analysis."""
1218 return expr.getAttributeNS(NSS['lut'], u'possible', '') == u'yes'
1219
1221 """Return all lookup tables used directly in computing this node.
1222
1223 If this is an expression node, checks all its children for table
1224 lookups, and returns the set of table indices used.
1225 """
1226 result = set()
1227 if isinstance(node, amara.bindery.element_base):
1228 if self.is_lookup_table(node):
1229 result.add(node.table_index)
1230 else:
1231 for child in node.xml_children:
1232 result.update(self.contained_table_indices(child))
1233 return result
1234
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.
1237
1238 Will return the empty string unless linear interpolation is being used.
1239 """
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
1244 else:
1245 factor = ''
1246 return factor
1247
1248 def output_table_lookup(self, expr, paren):
1249 """Output code to look up expr in the appropriate table."""
1250 i = expr.table_index
1251 if self.row_lookup_method:
1252 self.write('_lt_', i, '_row[', expr.table_name, ']')
1253 else:
1254 self.write(self.lookup_method_prefix, '_lookup_', expr.table_name,
1255 '(_table_index_', i, self.lut_factor(i, include_comma=True), ')')
1256 return
1257
1258 def output_table_index_generation(self, time_name, nodeset=set()):
1259 """Output code to calculate indexes into any lookup tables.
1260
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
1263 defined.
1264
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.
1267
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.
1271 """
1272 tables_to_index = set()
1273 nodes_used = set()
1274 for node in nodeset:
1275 tables_to_index.update(self.contained_table_indices(node))
1276 if tables_to_index or not nodeset:
1277 self.output_comment('Lookup table indexing')
1278 for key, idx in self.doc.lookup_table_indexes.iteritems():
1279 if not nodeset or idx in tables_to_index:
1280 var = key[-1]
1281 if var.get_type() is VarTypes.Computed:
1282 if not nodeset:
1283 raise TranslationError('Unable to generate lookup table indexed on', var, 'as it is a computed variable')
1284 var_nodes = self.calculate_extended_dependencies([var]) & nodeset
1285 self.output_equations(var_nodes)
1286 nodes_used.update(var_nodes)
1287 self.output_table_index_checking(key, idx)
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'
1293 else:
1294 dump_state_args = 'rY, ' + time_name
1295 self.writeln('EXCEPTION(DumpState("', self.var_display_name(key[-1]),
1296 ' outside lookup table range", ', dump_state_args,'));', indent_offset=1)
1297 self.writeln('#undef COVERAGE_IGNORE', indent=False)
1299 self.writeln()
1300 return nodes_used
1301
1302 def output_table_index_checking(self, key, idx):
1303 """Check whether a table index is out of bounds."""
1304 if self.config.options.check_lt_bounds:
1305 var = key[-1]
1306 min, max, _, _ = self.lut_parameters(key)
1307 varname = self.code_name(var)
1308 self.writeln('bool _oob_', idx, self.EQ_ASSIGN, 'false', self.STMT_END)
1309 self.writeln('if (', varname, '>', max, ' || ', varname, '<', min, ')')
1310 self.open_block()
1311 self.writeln('#define COVERAGE_IGNORE', indent=False)
1313 self.writeln('if (', varname, '>', max, ') ', varname, self.EQ_ASSIGN, max, self.STMT_END)
1314 self.writeln('else ', varname, self.EQ_ASSIGN, min, self.STMT_END)
1315 else:
1316 self.writeln('_oob_', idx, self.EQ_ASSIGN, 'true', self.STMT_END)
1317 self.writeln('#undef COVERAGE_IGNORE', indent=False)
1318 self.close_block(blank_line=False)
1319
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 '
1325 var = key[-1]
1326 min, max, _, step_inverse = self.lut_parameters(key)
1327 offset = '_offset_' + idx
1328 offset_over_step = offset + '_over_table_step'
1329 varname = self.code_name(var)
1330 self.writeln(self.TYPE_CONST_DOUBLE, offset, self.EQ_ASSIGN, varname, ' - ', min, self.STMT_END)
1331 self.writeln(self.TYPE_CONST_DOUBLE, offset_over_step, self.EQ_ASSIGN,
1332 offset, ' * ', step_inverse, self.STMT_END)
1333 idx_var = '_table_index_' + str(idx)
1334 if self.config.options.lookup_type == 'nearest-neighbour':
1335 if self.lt_index_uses_floor:
1336 self.writeln(index_type, idx_var, ' = (unsigned) round(', offset_over_step, ');')
1337 else:
1338 self.writeln(index_type, idx_var, ' = (unsigned) (', offset_over_step, '+0.5);')
1339 else:
1340 if self.lt_index_uses_floor:
1341 self.writeln(index_type, idx_var, ' = (unsigned) floor(', offset_over_step, ');')
1342 else:
1343 self.writeln(index_type, idx_var, ' = (unsigned)(', offset_over_step, ');')
1344 factor = self.lut_factor(idx)
1345 if factor:
1346 self.writeln(factor_type, factor, ' = ', offset_over_step, ' - ', idx_var, self.STMT_END)
1347 if self.row_lookup_method:
1348 self.writeln(row_type, '_lt_', idx, '_row = ', self.lookup_method_prefix, '_lookup_', idx,
1349 '_row(', idx_var, self.lut_factor(idx, include_comma=True), ');')
1350
1351
1352
1353
1354###############################################
1355# Register translation classes in this module #
1356###############################################
1357
1358
1359class SolverInfo(object):
1360 """Add information for specialised translator classes into a model."""
1361 def __init__(self, model, force=False):
1362 """Add information for the solvers as XML.
1363
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.
1367
1368 If any of these elements exist in the model they will be left
1369 unaltered, unless force is set to True.
1370
1371 This constructor just sets up the container element; call one
1372 of the add_* methods to actually add the information to it.
1373 """
1374 self._model = model
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
1379 else:
1380 solver_info = model.xml_create_element(u'solver_info', NSS[u'solver'])
1381 model.xml_append(solver_info)
1382 self._solver_info = solver_info
1383 self._component = None
1384 self._dt = None
1385
1386 def add_all_info(self):
1387 """Actually add the info."""
1390 self.add_linearised_odes()
1391 self.add_jacobian_matrix()
1392 self.add_dt_reference()
1393
1395 """Add a reference to the variable representing dt."""
1396 solver_info = self._solver_info
1397 model = self._model
1398 if not hasattr(solver_info, u'dt'):
1399 dt = self.get_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)
1403
1405 """The name of the transmembrane potential."""
1406 solver_info = self._solver_info
1407 model = self._model
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)
1413
1415 """Linearised ODEs - where du/dt = g + hu (and g, h are not functions of u).
1416
1417 Structure looks like:
1418 <linear_odes>
1419 <math>
1420 <apply><eq/>
1421 <apply><diff/>
1422 <bvar><ci>t</ci></bvar>
1423 <ci>u</ci>
1424 </apply>
1425 <apply><plus/>
1426 g
1427 <apply><times/>
1428 h
1429 <ci>u</ci>
1430 </apply>
1431 </apply>
1432 </apply>
1433 .
1434 .
1435 .
1436 </math>
1437 </linear_odes>
1438 """
1439 solver_info = self._solver_info
1440 model = self._model
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))
1455 # Ensure that the model has a special component
1457
1458 def _fix_jac_var_name(self, vname):
1459 """
1460 If PE will be performed on a model with a single component, then we'll need full names in
1461 the variable attributes.
1462 """
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:])
1465 else:
1466 name = unicode(vname)
1467 return name
1468
1470 """Jacobian matrix elements.
1471
1472 Structure looks like:
1473 <jacobian>
1474 [<math> assignments of common sub-terms </math>]
1475 <entry var_i='varname' var_j='varname'>
1476 <math> apply|cn|ci ...</math>
1477 </entry>
1478 </jacobian>
1479 """
1480 solver_info = self._solver_info
1481 model = self._model
1482 if model._cml_jacobian and model._cml_jacobian_full:
1483 jac = model._cml_jacobian[1]
1484 else:
1485 # Old-style partial jacobian, or no jacobian
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)
1490
1491 if model._cml_jacobian_full:
1492 # There may be temporaries
1493 temporaries = model._cml_jacobian[0]
1494 if temporaries:
1495 jac_elt.xml_append(amara_parse_cellml(temporaries).math)
1496
1497 jac_vars = jac.keys()
1498 jac_vars.sort() # Will sort by variable name
1499 for v_i, v_j in jac_vars:
1500 # Add (i,j)-th entry
1501 attrs = {u'var_i': self._fix_jac_var_name(v_i),
1502 u'var_j': self._fix_jac_var_name(v_j)}
1503 entry = model.xml_create_element(u'entry', NSS[u'solver'], attributes=attrs)
1504 jac_elt.xml_append(entry)
1505 entry_doc = amara_parse_cellml(jac[(v_i, v_j)].xml())
1506 entry.xml_append(entry_doc.math)
1507 # Ensure that the model has a special component
1509 return
1510
1512 """
1513 PE has just been performed, so we need to update variable names occurring outside
1514 the modifiable mathematics sections.
1515 """
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)
1520 var = self._get_variable(vname)
1521 new_name = var.get_source_variable(recurse=True).fullname()
1522 setattr(entry, vlabel, new_name)
1523 dt_elt = getattr(self._solver_info, u'dt', None)
1524 if dt_elt:
1525 var = self._get_variable(unicode(dt_elt))
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))
1529
1531 """Add ionic current information as XML for solvers to use."""
1532 solver_info = self._solver_info
1533 model = self._model
1534 # The total ionic current. This relies on having a configuration store.
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'])
1539 # Adds each ionic var to the xml doc from the config store
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)
1545 return
1546
1548 """Add the update equations for the linear ODEs.
1549
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).
1552
1553 This replaces the linear_odes block with the structure:
1554 <linear_odes>
1555 <math>
1556 <ci>u</ci>
1557 <ci>t</ci>
1558 <apply> <!-- (u + g.dt)/(1 - h.dt) --> </apply>
1559 </math>
1560 .
1561 .
1562 .
1563 </linear_odes>
1564 """
1565 block = getattr(self._solver_info, u'linear_odes', None)
1566 dt = self._model.get_config().dt_variable.fullname() # was dt = u'delta_t'
1567 # Add the new equations
1568 for u, t, gh in self.get_linearised_odes():
1569 g, h = gh
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)
1582 self._add_variable_links(math)
1583 # Remove the old equations (first math element)
1584 block.xml_remove_child(block.math)
1585
1587 """Link ci elements in the added XML to cellml_variable objects.
1588
1589 This analyses the names in the ci elements to determine which variable in
1590 the model they refer to.
1591 """
1593 #1795 - classify temporary variables for the Jacobian matrix, and append
1594 # to the main list of assignments in the model
1595 solver_info = self._solver_info
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)
1601 #2418 - check if any state variables have been units-converted
1603
1605 """Check if any Jacobian entries need to be altered because the units of state variables have changed.
1606
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.
1610 """
1611 if not hasattr(self._solver_info, u'jacobian'):
1612 return
1613 # Helper methods
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."""
1616 if vars is None:
1617 vars = []
1618 if isinstance(elt, mathml_ci):
1619 elt.variable.set_value(1.0)
1620 vars.append(elt.variable)
1621 else:
1622 for child in getattr(elt, 'xml_children', []):
1623 set_var_values(child, vars)
1624 return vars
1625 # Find any converted state variables
1626 converted_state_vars = set()
1627 for entry in getattr(self._solver_info.jacobian, u'entry', []):
1628 var = self._get_variable(entry.var_i)
1629 if var.get_type() == VarTypes.Computed:
1630 converted_state_vars.add(var)
1631 if not converted_state_vars:
1632 return
1633 # Figure out the conversion factor in each case
1634 state_var_map = {}
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()
1642 # Apply the conversion to relevant Jacobian entries
1643 for entry in getattr(self._solver_info.jacobian, u'entry', []):
1644 factor = 1
1645 var_i = self._get_variable(entry.var_i)
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())
1650 factor /= factor_i
1651 var_j = self._get_variable(entry.var_j)
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())
1656 factor *= factor_j
1657 if factor != 1:
1658 # Replace rhs with rhs * factor
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)
1663
1665 """Do a binding time analysis on the additional mathematics.
1666
1667 This requires self.add_variable_links to have been called already.
1668 """
1669 self._process_mathematics(lambda elt: elt._get_binding_time())
1670
1671 def _process_mathematics(self, func):
1672 """Apply func to each top-level mathematical construct in the solver info blocks.
1673
1674 func must be able to accept mathml_piecewise, mathml_apply, mathml_ci and mathml_cn elements.
1675 """
1676 solver_info = self._solver_info
1677 # Jacobian
1678 if hasattr(solver_info, u'jacobian'):
1679 if hasattr(solver_info.jacobian, u'math'):
1680 for elt in solver_info.jacobian.math.apply:
1681 func(elt)
1682 for entry in solver_info.jacobian.entry:
1683 for elt in entry.math.xml_element_children():
1684 func(elt)
1685 # Linearised ODEs
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():
1689 func(elt)
1690
1692 """Check if the solver info blocks contain any modifiable mathematics."""
1693 try:
1694 self.get_modifiable_mathematics().next()
1695 return True
1696 except StopIteration:
1697 return False
1698
1700 """Get an iterable over mathematical constructs in the solver info blocks that can be changed.
1701
1702 Returned elements will be mathml_piecewise, mathml_apply, mathml_ci or mathml_cn instances.
1703 """
1704 solver_info = self._solver_info
1705 # Jacobian - entry definitions and temporaries can be changed
1706 if hasattr(solver_info, u'jacobian'):
1707 if hasattr(solver_info.jacobian, u'math'):
1708 for elt in solver_info.jacobian.math.apply:
1709 yield elt
1710 for entry in solver_info.jacobian.entry:
1711 for elt in entry.math.xml_element_children():
1712 yield elt
1713 # Linearised ODEs - only g & h can be changed
1714 if hasattr(solver_info, u'linear_odes'):
1715 for _, _, eqns in self.get_linearised_odes():
1716 for eqn in eqns:
1717 yield eqn
1718
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).
1722
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).
1726 """
1727 if hasattr(self._solver_info, u'linear_odes'):
1728 if hasattr(self._solver_info.linear_odes.math, u'ci'):
1729 for math in self._solver_info.linear_odes.math:
1730 u, t, eqn = list(math.xml_element_children())
1731 u = u.variable
1732 t = t.variable
1733 yield (u, t, (eqn,))
1734 else:
1735 for ode in self._solver_info.linear_odes.math.apply:
1736 u = ode.apply.ci.variable
1737 t = ode.apply.bvar.ci.variable
1738 opers = ode.apply[1].operands()
1739 g = opers.next()
1740 h = opers.next().operands().next()
1741 yield (u, t, (g,h))
1742
1743 def _add_variable_links(self, elt):
1744 """Recursively link ci elements in the given XML tree to cellml_variable objects.
1745
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.
1748 """
1749 if isinstance(elt, mathml_ci):
1750 var = self._get_variable(unicode(elt))
1751 elt._cml_variable = var
1752 elt._cml_component = var.component
1753 elif isinstance(elt, mathml_cn):
1754 # Fake a component, since it doesn't really have one
1755 elt._cml_component = elt.model.component
1756 elif hasattr(elt, 'xml_children'):
1757 for child in elt.xml_children:
1758 self._add_variable_links(child)
1759
1760 _jac_temp_re = re.compile(r't[0-9]+')
1761 def _get_variable(self, varname):
1762 """Return the variable in the model with name varname."""
1763 try:
1764 if varname == 'delta_t':
1765 # Special case for the timestep in ComputeJacobian and elsewhere
1766 var = self.get_dt()
1767 elif self._jac_temp_re.match(varname):
1768 var = self._get_special_variable(varname, VarTypes.Unknown)
1769 else:
1770 var = cellml_variable.get_variable_object(self._model, varname)
1771 except KeyError:
1772 raise ValueError("Cannot find variable '%s' referenced in SolverInfo" % varname)
1773 return var
1774
1775 def create_dt(self, modifier, comp, units):
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)
1779 return self._dt
1780
1781 def get_dt(self):
1782 """Get or create a special 'dt' variable."""
1783 if not self._dt:
1784 self._dt = self._get_special_variable(u'dt', VarTypes.Free)
1785 return self._dt
1786
1787 def _get_special_variable(self, varname, ptype=VarTypes.Unknown):
1788 """Get or create a special variable object that doesn't really exist in the model."""
1789 comp = self._get_special_component()
1790 try:
1791 var = comp.get_variable_by_name(varname)
1792 except KeyError:
1793 var = cellml_variable.create_new(self._model, varname, u'dimensionless')
1794 comp._add_variable(var)
1795 var._set_type(ptype)
1796 return var
1797
1799 """Get or create a special component for containing special variables."""
1800 if not self._component:
1801 self._component = cellml_component.create_new(self._model, u'')
1802 self._model._add_component(self._component, special=True)
1803 return self._component
1804
1805
1806
1808 """
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.
1812 """
1813 def __init__(self, doc, options=None):
1814 """Create a new store.
1815
1816 doc specifies a CellML document, the processing of which this configuration store will affect.
1817
1818 If given, options should be an optparse.Values instance containing command-line options.
1819 """
1820 self.doc = doc
1821 doc._cml_config = self
1822 self.options = options
1823 self.unit_definitions = cellml_component.create_new(doc.model, '*lookup_table_units*')
1824 self.unit_definitions.xml_parent = doc.model # Needed for looking up standard units
1825 # Transmembrane potential
1826 self.V_definitions = [u'membrane,V']
1827 self.V_variable = None
1828 # Membrane capacitance
1830 self.Cm_variable = None
1831 # Lookup table configuration
1832 self.lut_config = {}
1833 # Ionic currents configuration
1834 self.i_stim_definitions = [u'membrane,i_Stim']
1835 self.i_stim_var = None
1836 self.i_ionic_definitions = [u'membrane,i_.*']
1838 # Whether GetIIonic will need to negate the sum of i_ionic_vars
1839 self.i_ionic_negated = False
1840 # Whether the stimulus magnitude is positive, rather than negative
1841 self.i_stim_negated = False
1842 # Other variables that may be set by other code, for example an InterfaceGenerator
1843 self.dt_variable = None
1846 return
1847
1848 def read_configuration_file(self, config_file):
1849 """Read configuration stored in config_file.
1850
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.
1855
1856 The root element may contain a 'global' element, giving global
1857 configuration options. These include:
1858
1859 * 'lookup_tables'
1860 Contains one or more 'lookup_table' elements, one for each
1861 type of lookup table available. These contain (a selection of)
1862 the elements:
1863 * 'var' - the variable to key on. The component name given
1864 should be that from which the variable is exported. Must be
1865 present.
1866 * 'min', 'max', 'step' - table bounds parameters. Optional.
1867 Default values are used for parameters that are not present.
1868 * 'currents'
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.
1878
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
1883 definition be used.
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.
1889
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
1895 specified globally.
1896
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'.
1902
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>
1910
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.
1914
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.
1917 """
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)
1924 # Store extra units definitions
1925 for defn in config_doc.xml_xpath(u'/*/units'):
1926 defn.xml_parent = self.unit_definitions # Needed for looking up the units this definition is derived from
1927 self.unit_definitions.add_units(defn.name, defn)
1928 # Overrides for command-line options
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')
1932 get_options(args, self.options)
1933 # Sections to use in configuration; later sections take precedence
1934 sections = []
1935 # Use global configuration?
1936 glo = config_doc.xml_xpath(u'/*/global')
1937 if glo:
1938 sections.append(glo[0])
1939 # Get the config section(s) for our model. Sections
1940 # specifically for this model come after sections covering
1941 # multiple models, so they take precedence.
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)))
1949 # Main items of configuration
1950 for section in sections:
1951 if hasattr(section, u'lookup_tables'):
1952 self._parse_lookup_tables(section.lookup_tables)
1953 if hasattr(section, u'currents'):
1954 self._parse_currents(section.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)
1959
1961 """Having read all the configuration files, apply to the model."""
1962 # If no LT options given, add defaults
1963 if not self.lut_config:
1964 config_key = ('config-name', 'transmembrane_potential')
1965 self.lut_config[config_key] = {}
1966 self._set_lut_defaults(self.lut_config[config_key])
1967 # Identify the variables in the model
1970 if not self.options.protocol:
1971 self.find_current_vars()
1972
1973 def _create_var_def(self, content, defn_type):
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
1977
1978 def _check_var_def(self, var_elt, var_desc):
1979 """Check a variable definition is syntactically valid.
1980
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'.
1984 """
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:
1989 raise ConfigurationError('Invalid definition of ' + var_desc + ': '
1990 + unicode(var_elt))
1991 elif defn_type == u'oxmeta':
1992 if unicode(var_elt) not in cellml_metadata.METADATA_NAMES:
1993 raise ConfigurationError('"' + unicode(var_elt) + '" is not a valid oxmeta name')
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')
1997 else:
1998 raise ConfigurationError('"' + defn_type + '" is not a valid variable definition type')
1999 return
2000
2001 def _parse_var(self, elt, name):
2002 """Parse definition of a special variable."""
2003 if hasattr(elt, 'var'):
2004 # List of possibilities
2005 defs = []
2006 for vardef in elt.var:
2007 self._check_var_def(vardef, name)
2008 defs.append(vardef)
2009 else:
2010 # Old style - single variable given by text content
2011 self._check_var_def(elt, name)
2012 defs = [elt]
2013 return defs
2014
2015 def _parse_Vm(self, vm_elt):
2016 """Parse definition of variable holding the transmembrane potential."""
2017 self.V_definitions = self._parse_var(vm_elt, 'transmembrane_potential')
2018
2019 def _parse_Cm(self, cm_elt):
2020 """Parse definition of variable holding the cell membrane capacitance."""
2021 self.Cm_definitions = self._parse_var(cm_elt, 'membrane_capacitance')
2022
2023 def _parse_currents(self, currents):
2024 """Parse definitions of ionic and stimulus currents."""
2025 if hasattr(currents, u'stimulus'):
2026 self.i_stim_definitions = self._parse_var(currents.stimulus, 'stimulus current')
2027 if hasattr(currents, u'ionic_match'):
2028 self.i_ionic_definitions = self._parse_var(currents.ionic_match, 'ionic currents')
2029 return
2030
2031 def _find_variable(self, defn, pe_done=False):
2032 """Find a variable matching the given definition.
2033
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.
2037 """
2038 defn_type = getattr(defn, u'type', u'name')
2039 if defn_type == u'name':
2040 name_parts = unicode(defn).strip().split(',')
2041 if pe_done:
2042 try:
2043 var = self.doc.model.component.get_variable_by_name(u'__'.join(name_parts))
2044 except KeyError:
2045 var = None
2046 else:
2047 var = self.doc.model.xml_xpath(u'cml:component[@name="%s"]/cml:variable[@name="%s"]'
2048 % tuple(name_parts))
2049 if var:
2050 var = var[0]
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':
2055 var = self.i_stim_var
2056 elif unicode(defn) == u'transmembrane_potential':
2057 var = self.V_variable
2058 elif unicode(defn) == u'membrane_capacitance':
2059 var = self.Cm_variable
2060 else:
2061 raise ConfigurationError('"' + str(defn) + '" is not a valid configuration file variable name')
2062 else:
2063 raise ConfigurationError('"' + defn_type + '" is not a valid variable definition type')
2064 return var
2065
2066 def _process_ci_elts(self, elt, func, **kwargs):
2067 """Recursively apply func to any ci elements in the tree rooted at elt."""
2068 if isinstance(elt, mathml_ci):
2069 func(elt, **kwargs)
2070 else:
2071 for child in getattr(elt, 'xml_children', []):
2072 self._process_ci_elts(child, func, **kwargs)
2073
2075 """Analyse the expression for dV/dt to determine the transmembrane currents.
2076
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.
2082
2083 If self.V_variable is not set, returns the empty list.
2084 """
2085 if not self.V_variable:
2086 DEBUG('config', "Transmembrane potential not configured, so can't determine currents from its ODE")
2087 return []
2088 if self.i_stim_var:
2089 current_units = [self.i_stim_var.component.get_units_by_name(self.i_stim_var.units)]
2090 else:
2091 from CellMLToNektarTranslator import CellMLToNektarTranslator
2092 current_units = CellMLToNektarTranslator.get_current_units_options(self.doc.model)
2093 ionic_vars = []
2094
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.
2097
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.
2101 """
2102 for units in units_list:
2103 if test_units.dimensionally_equivalent(units):
2104 match = units
2105 break
2106 else:
2107 match = None
2108 if match and remove_match:
2109 units_list.remove(match)
2110 if match and keep_only_match:
2111 units_list[:] = [match]
2112 return match
2113
2114 def clear_values(expr, process_definitions=False):
2115 """Recursively clear saved values for variables in this expression.
2116
2117 If process_definitions is True, recursively treat expressions defining variables
2118 used in this expression, too.
2119 """
2120 def process_var(var):
2121 var.unset_values()
2122 var._unset_binding_time(only_temporary=True)
2123 if process_definitions:
2124 defn = var.get_dependencies()
2125 if defn:
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)
2132 self._process_ci_elts(expr, process_ci)
2133
2134 def check_if_current(ci_elt, vars_found):
2135 """Check if this is a transmembrane current."""
2136 v = ci_elt.variable
2137 if v.get_source_variable(recurse=True) is not self.i_stim_var:
2138 vars_found.append(v)
2139 # Check units
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 # Hack for data clamp support (#2708)
2144 # Fake this variable being 1 so we can check the sign of GetIIonic
2145 if not v.is_statically_const(ignore_annotations=True):
2146 v.set_value(1.0)
2147
2148 def bfs(func, vars, *args, **kwargs):
2149 """Do a breadth first search of the definitions of variables in vars.
2150
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.
2153 """
2154 def get_defn(var):
2155 defn = var.get_dependencies()
2156 if defn:
2157 var._set_binding_time(BINDING_TIMES.static, temporary=True)
2158 if isinstance(defn[0], cellml_variable):
2159 defn = get_defn(defn[0])
2160 else:
2161 assert isinstance(defn[0], mathml_apply)
2162 var.unset_values()
2163 defn = defn[0].eq.rhs
2164 return defn
2165 defns = []
2166 for var in vars:
2167 defn = get_defn(var)
2168 if defn:
2169 defns.append(defn)
2170 if defns:
2171 func(defns, *args, **kwargs)
2172
2173 def find_currents(exprs, depth=0, maxdepth=2):
2174 """Find ionic currents by searching the given expressions.
2175
2176 On the initial call, exprs should contain just the definition of dV/dt (i.e. the RHS).
2177
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.
2181
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.
2187
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.
2191 """
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)
2195# # We could do this check, but actually it doesn't catch much and later checks will pick up on the problem
2196# if A_per_F is None and not self.i_stim_var:
2197# raise ConfigurationError('Units ' + dvdt_units.description() + ' of dV/dt are not equivalent to V/s - unable to continue.')
2198 # Process all expressions at this depth
2199 vars_found = []
2200 for expr in exprs:
2201 self._process_ci_elts(expr, check_if_current, vars_found=vars_found)
2202 if not ionic_vars and depth != maxdepth:
2203 # Process the definitions of expressions at this depth
2204 bfs(find_currents, vars_found, depth+1, maxdepth)
2205 # If we reached maxdepth unsuccessfully, try again with A_per_F included (if it was an option)
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)
2209
2210 def assign_values_for_stimulus_check(exprs, found_stim=Sentinel()):
2211 """Assign temporary values to variables in order to check the stimulus sign.
2212
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.
2221 """
2222 assert len(current_units) == 1 # We are using the stimulus units
2223 vars = []
2224 def f(ci_elt):
2225 v = ci_elt.variable
2226 if v.get_source_variable(recurse=True) is self.i_stim_var:
2227 v.set_value(1.0)
2228 found_stim.set()
2229 else:
2230 u = v.component.get_units_by_name(v.units)
2231 if u.dimensionally_equivalent(current_units[0]):
2232 v.set_value(0.0)
2233 elif not v.is_statically_const(ignore_annotations=True):
2234 v.set_value(1.0)
2235 vars.append(v)
2236 for expr in exprs:
2237 self._process_ci_elts(expr, f)
2238 if not found_stim:
2239 bfs(assign_values_for_stimulus_check, vars, found_stim=found_stim)
2240
2241 # Iterate over all expressions in the model, to find the one for dV/d(something)
2242 for expr in (e for e in self.doc.model.get_assignments() if isinstance(e, mathml_apply) and e.is_ode()):
2243 # Assume the independent variable is time; if it isn't, we'll catch this later
2244 (dep_var, time_var) = expr.assigned_variable()
2245 if dep_var.get_source_variable(recurse=True) is self.V_variable:
2246 # Recursively search for ionic currents
2247 find_currents([expr.eq.rhs])
2248 if not ionic_vars:
2249 # The sign checks below will be nonsense in this case. An error will be raised later.
2250 break
2251 # Check the sign of the RHS
2252 self.i_ionic_negated = expr.eq.rhs.evaluate() > 0.0
2253 clear_values(expr.eq.rhs, process_definitions=True)
2254 if self.i_stim_var:
2255 # Check the sign of the stimulus current
2256 assign_values_for_stimulus_check([expr.eq.rhs])
2257 self.i_stim_negated = expr.eq.rhs.evaluate() > 0.0
2258 clear_values(expr.eq.rhs, process_definitions=True)
2259 # Found dV/d(something); don't check any more expressions
2260 break
2261 DEBUG('config', "Found ionic currents from dV/dt: ", ionic_vars)
2262 call_if(self.i_ionic_negated, DEBUG, 'config', "Ionic current is negated")
2263 call_if(self.i_stim_negated, DEBUG, 'config', "Stimulus current is negated")
2264 return ionic_vars
2265
2266 def _find_var(self, oxmeta_name, definitions):
2267 """Find the variable object in the model for a particular concept.
2268
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.
2271 """
2272 var = None
2273 # Prepend an oxmeta definition
2274 oxmeta_defn = self._create_var_def(oxmeta_name, 'oxmeta')
2275 for defn in [oxmeta_defn] + definitions:
2276 var = self._find_variable(defn)
2277 if var:
2278 break
2279 return var
2280
2282 """Find the variables representing currents."""
2283 # Find the stimulus current, if it exists for this kind of model (some are self-excitatory)
2284 if not self.doc.model.is_self_excitatory():
2285 # self.i_stim_var = self._find_var('membrane_stimulus_current', self.i_stim_definitions)
2286 # DEBUG('config', 'Found stimulus', self.i_stim_var)
2287 if not self.i_stim_var:
2288 # No match :(
2289 msg = "No stimulus current found; you'll have trouble generating Nektar code"
2290 if self.options.fully_automatic:
2291 raise ConfigurationError(msg)
2292 else:
2293 print >>sys.stderr, msg
2294 self.i_stim_var = None
2295 # For other ionic currents, try using the equation for dV/dt unless told otherwise
2296 if not self.options.use_i_ionic_regexp:
2298 else:
2299 for defn in self.i_ionic_definitions:
2300 if getattr(defn, u'type', u'name') != u'name':
2301 raise ConfigurationError('Ionic current definitions have to have type "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', []):
2308 if (var is not self.i_stim_var and
2309 var_re.match(unicode(var.name).strip())):
2310 self.i_ionic_vars.append(var)
2311 if not self.i_ionic_vars:
2312 msg = "No ionic currents found; you'll have trouble generating Nektar code"
2313 if self.options.fully_automatic:
2314 raise ConfigurationError(msg)
2315 else:
2316 print >>sys.stderr, msg
2317 return
2318
2319 def _parse_lookup_tables(self, lookup_tables):
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)
2325 if not config_key in self.lut_config:
2326 self.lut_config[config_key] = {}
2327 self._set_lut_defaults(self.lut_config[config_key])
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'):
2332 try:
2333 units = self.unit_definitions.get_units_by_name(lt.units)
2334 except KeyError:
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
2338 return
2339
2340 def _set_lut_defaults(self, lut_dict):
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':
2345 lut_dict[k] = v
2346 lut_dict['table_units'] = None
2347 return
2348
2350 """Annotate ionic & stimulus current vars so PE doesn't remove them.
2351 Also annotate the membrane capacitance, if defined."""
2352 if self.i_stim_var:
2353 self.i_stim_var.set_pe_keep(True)
2354 for var in self.i_ionic_vars:
2355 var.set_pe_keep(True)
2356 if self.Cm_variable:
2357 self.Cm_variable.set_pe_keep(True)
2358 return
2359
2361 """Expose variables for access with GetAnyVariable if desired."""
2362 def annotate(var):
2363 t = var.get_type()
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:
2369 for var in self.metadata_vars:
2370 if (not self.options.use_chaste_stimulus or
2371 not var.oxmeta_name in cellml_metadata.STIMULUS_NAMES):
2372 annotate(var)
2373 DEBUG('translate', "+++ Exposed annotated variables")
2374 if self.options.expose_all_variables:
2375 for var in self.doc.model.get_all_variables():
2376 annotate(var)
2377 DEBUG('translate', "+++ Exposed all variables")
2378
2380 "Annotate all vars tagged with metadata so PE doesn't remove them."
2381 for var in self.metadata_vars:
2382 var.set_pe_keep(True)
2383 return
2384
2386 """Find and store the variable object representing V.
2387
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.
2390 """
2391 if not self.options:
2392 raise ValueError('No command line options given')
2393 # Check command line option before config file
2394 if self.options.transmembrane_potential:
2395 self.V_definitions[0:0] = [self.options.transmembrane_potential.strip().split(',')]
2396 if len(self.V_definitions[0]) != 2:
2397 raise ConfigurationError('The name of V must contain both component and variable name')
2398 self.V_variable = self._find_var('membrane_voltage', self.V_definitions)
2399 DEBUG('config', 'Found V', self.V_variable)
2400 if not self.V_variable and not self.options.protocol:
2401 raise ConfigurationError('No transmembrane potential found; check your configuration')
2402 return self.V_variable
2403
2405 """Find and store the variable object representing the cell membrane capacitance.
2406
2407 Uses first metadata, if present, then the configuration file."""
2408 self.Cm_variable = self._find_var('membrane_capacitance', self.Cm_definitions)
2409 DEBUG('config', 'Found capacitance', self.Cm_variable)
2410
2412 """Find the variable objects used as lookup table keys.
2413
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).
2418
2419 The table settings are also units-converted to match the units of the key variable.
2420 """
2421 new_config = {}
2422 for key in self.lut_config:
2423 defn_type, content = key
2424 defn = self._create_var_def(content, defn_type)
2425 var = self._find_variable(defn)
2426 if not var:
2427 # Variable doesn't exist, so we can't index on it
2428 LOG('lookup-tables', logging.WARNING, 'Variable', content, 'not found, so not using as table index.')
2429 else:
2430 var = var.get_source_variable(recurse=True)
2431 if not var in new_config:
2432 new_config[var] = {}
2433 new_config[var].update(self.lut_config[key])
2434 # Apply units conversions to the table settings if required
2435 table_units = new_config[var]['table_units']
2436 if 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):
2442 # New setting[var_units] = m[var_units/table_units]*(setting-o1[table_units]) + o2[var_units]
2443 # c.f. mathml_units_mixin._add_units_conversion
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]:
2447 try:
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):
2453 pass
2454 self.lut_config = new_config
2455 DEBUG('config', 'Lookup tables configuration:', new_config)
2456 return
2457
2458 # TODO - move into seperate metadata class?
2459 def validate_metadata(self, assume_valid=False):
2460 """Check that the metadata annotations are 'sensible'.
2461
2462 Ensures that only names we know are used, and that the same name isn't used for multiple variables.
2463 """
2464 vars = cellml_metadata.find_variables(self.doc.model, ('bqbiol:is', NSS['bqbiol']))
2465 self.metadata_vars = filter(lambda v: v.oxmeta_name, vars)
2466 if assume_valid:
2467 return
2468 names_used = [var.oxmeta_name for var in self.metadata_vars]
2469 DEBUG('metadata', 'Names found: ', names_used)
2470 # Check all metadata is allowed
2471 unknown_names = frozenset(names_used) - cellml_metadata.METADATA_NAMES
2472 if unknown_names:
2473 msg = ['Unrecognised oxmeta variable names found (run with --assume-valid to ignore):']
2474 msg.extend(sorted(unknown_names))
2475 raise ConfigurationError('\n '.join(msg))
2476 # Check for duplicates
2477 d = {}
2478 for name in names_used:
2479 if name in d:
2480 raise ConfigurationError(name + ' metadata attribute is duplicated in the cellml file.')
2481 else:
2482 d[name] = name
2483
2484
2485######################################################################
2486# For running as an executable #
2487######################################################################
2488
2489def get_options(args, default_options=None):
2490 """get_options(args):
2491 Process our command-line options.
2492
2493 args is a list of options & positional arguments.
2494
2495 default_options, if given, is an instance of optparse.Values created by a
2496 previous call to this function.
2497 """
2498 usage = 'usage: %prog [options] <cellml file or URI>'
2499 parser = optparse.OptionParser(version="%%prog %s" % __version__,
2500 usage=usage)
2501 parser.add_option('-q', '--quiet', action='store_true', default=False,
2502 help="don't show warning messages, only errors")
2503 # What type of translation is being performed
2504 parser.add_option('-T', '--translate',
2505 dest='translate', action='store_true',
2506 default=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]")
2519 # Global adjustment settings
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")
2547 # To examine the profile do something like:
2548 # import os,pstats
2549 # os.chdir('/tmp')
2550 # files = filter(lambda f: f.startswith('pycml'), os.listdir('.'))
2551 # p = pstats.Stats(*files)
2552 # p.strip_dirs().sort_stats('cum').print_stats(15)
2553 # What optimisations/transformations to do
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."
2576 " Requires -j.")
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)
2592 # Settings tweaking the generated code
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',
2598 default=False,
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)
2604 # Options specific to Maple output
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)
2614 # Options specific to Python output
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)
2619 # Options specific to Chaste output
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)
2659 # Options specific to Functional Curation
2660 group = optparse.OptionGroup(parser, 'Functional Curation options', "Options specific to use by Functional Curation")
2661 def protocol_callback(option, opt_str, value, parser):
2662 """
2663 Protocols don't always produce normal cardiac cell models.
2664 However, we want to allow a later option to override these changes.
2665 """
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)
2678 # Settings for lookup tables
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)
2709 # Settings for partial evaluation
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)
2721
2722 options, args = parser.parse_args(args, values=default_options)
2723 if len(args) != 1:
2724 parser.error("exactly one input CellML file must be specified")
2725
2726 # Some options imply others
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'.")
2744 # Numba may not be available
2745 if options.numba:
2746 try:
2747 import numba
2748 except:
2749 options.numba = False
2750
2751 return options, args[0]
2752
2753
2754def load_model(model_file, options):
2755 """Load and validate a CellML model."""
2756 # Setup logging
2757 logging.thread = None # Hack: we're not multi-threaded, so be slightly quicker...
2758 if options.debug:
2759 formatter = logging.Formatter(fmt="%(name)s: %(message)s")
2760 handler = logging.StreamHandler(sys.stderr)
2761 handler.setFormatter(formatter)
2762 handler.addFilter(OnlyDebugFilter())
2763 if options.debug_source:
2764 handler.addFilter(OnlyTheseSourcesFilter(options.debug_source))
2765 logging.getLogger().addHandler(handler)
2766 logging.getLogger().setLevel(logging.DEBUG)
2767
2768 # We can't translate if some warnings occur, as well as if the model is invalid
2769 notifier = NotifyHandler(level=logging.WARNING_TRANSLATE_ERROR)
2770 logging.getLogger('validator').addHandler(notifier)
2771 v = validator.CellMLValidator(create_relaxng_validator=not options.assume_valid)
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)
2776 v.quit()
2777 del v
2778
2779 if not valid or notifier.messages:
2780 print >>sys.stderr, model_file,
2781 if not valid:
2782 print >>sys.stderr, "is not a valid CellML file"
2783 else:
2784 print >>sys.stderr, "contains untranslatable constructs (see warnings above for details)"
2785 sys.exit(1)
2786
2787 return doc
2788
2789def run():
2790 """Translate the file given on the command line."""
2791 options, model_file = get_options(sys.argv[1:])
2792 doc = load_model(model_file, options)
2793 DEBUG('translate', "+++ Loaded model")
2794
2795 config = ConfigurationStore(doc, options=options)
2796 for config_file in options.config_file:
2797 config.read_configuration_file(config_file)
2798 DEBUG('translate', "+++ Read config")
2799
2800 # Apply protocol, if given
2801 if options.protocol:
2802 import protocol
2803 protocol.apply_protocol_file(doc, options.protocol)
2804 if options.debug:
2805 post_proto_cellml = options.outfilename or model_file
2806 post_proto_cellml = os.path.splitext(post_proto_cellml)[0] + '-proto.cellml.ppp'
2807 stream = open_output_stream(post_proto_cellml)
2808 doc.xml(indent=u'yes', stream=stream)
2809 close_output_stream(stream)
2810 DEBUG('translate', "+++ Applied protocol")
2811
2812 config.finalize_config()
2813 DEBUG('translate', "+++ Processed config")
2814
2815 solver_info = SolverInfo(doc.model)
2816
2817 # Generate an interface component, if desired
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")
2823
2824 if options.lut:
2825 config.find_lookup_variables()
2826 DEBUG('translate', "+++ Found LT keys")
2827
2828 # These bits could do with improving, as they annotate more than is really needed!
2829 if options.pe:
2830 # We need to ensure PE doesn't remove ionic currents needed for GetIIonic
2831 config.annotate_currents_for_pe()
2832 # "Need" to ensure pe doesn't remove metadata-annotated variables (when using modifiers or default stimulus?)
2833 config.annotate_metadata_for_pe()
2834 DEBUG('translate', "+++ Annotated variables")
2835 # Deal with the 'expose' options
2836 config.expose_variables()
2837
2838 class_name = options.class_name
2839 if not class_name:
2840 class_name = doc.model.name.replace('-', '_')
2841 if options.augment_class_name:
2842 class_name = u'CML_' + class_name
2843 if options.pe:
2844 class_name += '_pe'
2845 if options.lut:
2846 class_name += '_lut'
2847 if options.backward_euler:
2848 class_name += '_be'
2849 if options.use_modifiers:
2850 class_name += '_sens'
2851 if options.protocol:
2852 # Try to avoid OdeSystemInformation conflicts
2853 class_name += "_Proto_" + os.path.splitext(os.path.basename(options.protocol))[0]
2854
2855 output_filename = getattr(options, 'outfilename', None)
2856 if not options.translate and not output_filename:
2857 output_filename = 'stdout'
2858
2859 if options.units_conversions:
2860 doc.model.add_units_conversions()
2861 DEBUG('translate', "+++ Added units conversions")
2862
2863 if options.do_jacobian_analysis:
2865 lin.analyse_for_jacobian(doc, V=config.V_variable)
2866 DEBUG('translate', "+++ Analysed model for Jacobian")
2867
2868 if options.maple_output:
2869 # Parse Jacobian matrix
2870 from maple_parser import MapleParser
2871 mp = MapleParser()
2872 jacobian_file = file(options.maple_output) # TODO: Error checking
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:
2877 # Add full jacobian to XML
2878 solver_info.add_jacobian_matrix()
2879 solver_info.add_variable_links()
2880
2881 if options.backward_euler:
2882 # Rearrange linear ODEs
2884 lin.analyse_for_jacobian(doc, V=config.V_variable)
2885 lin.rearrange_linear_odes(doc)
2886 # Remove jacobian entries that don't correspond to nonlinear state variables
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])
2892 def gv(vname):
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:
2898 # Transform the Jacobian into the form needed by the Backward Euler code
2899 import maple_parser
2900 for key, expr in jacobian.iteritems():
2901 new_expr = None
2902 if key[0] == key[1]:
2903 # 1 on the diagonal
2904 new_expr = maple_parser.MNumber(['1'])
2905 if not (isinstance(expr, maple_parser.MNumber) and str(expr) == '0'):
2906 # subtract delta_t * expr
2907 args = []
2908 if new_expr:
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')
2912 if new_expr:
2913 jacobian[key] = new_expr
2914 # Add info as XML
2915 solver_info.add_all_info()
2916 # Analyse the XML, adding cellml_variable references, etc.
2917 solver_info.add_variable_links()
2918 solver_info.add_linear_ode_update_equations()
2919 DEBUG('translate', "+++ Parsed and incorporated Maple output")
2920 else:
2921 options.include_dt_in_tables = False
2922
2923 if options.lut:
2924 # Create the analyser so PE knows which variables are table keys
2926 else:
2927 lut = None
2928
2929 if options.pe:
2930 # Do partial evaluation
2932 pe.parteval(doc, solver_info, lut)
2933 DEBUG('translate', "+++ Done PE")
2934
2935 if options.lut:
2936 # Do the lookup table analysis
2937 lut.analyse_model(doc, solver_info)
2938 DEBUG('translate', "+++ Done LT analysis")
2939
2940 if options.rush_larsen:
2942 rl.analyse_model(doc)
2943 DEBUG('translate', "+++ Done Rush-Larsen analysis")
2944
2945 if options.translate:
2946 # Translate to code
2947 initargs = {'add_timestamp': not options.no_timestamp,
2948 'options': options}
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
2956 el"""
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)
2970 else:
2971 # Add a comment element
2972 comment = pycml.comment_base(
2973 body=u'\n' + version_comment(not options.no_timestamp) + u'\n')
2974 doc.xml_insert_before(doc.model, comment)
2975 # Output annotated model
2976 stream = open_output_stream(output_filename)
2977 doc.xml(indent=u'yes', stream=stream)
2978 close_output_stream(stream)
2979
2980
2981 DEBUG('translate', "+++ Done translation")
def output_file_name(self, model_filename)
Definition: translators.py:324
def __init__(self, add_timestamp=True, options=None)
Definition: translators.py:167
def output_variable(self, ci_elt, ode=False)
Definition: translators.py:642
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)
Definition: translators.py:207
def scan_for_lookup_tables(self)
Lookup table methods #.
Definition: translators.py:980
def set_indent(self, level=None, offset=None)
Definition: translators.py:431
def output_comment(self, *args, **kwargs)
Definition: translators.py:416
def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[])
Dependency related methods #.
Definition: translators.py:899
def output_single_lookup(self, tidx, tname, result)
def code_name(self, var, ode=False, prefix=None)
Definition: translators.py:442
def output_function(self, func_name, args, paren, reciprocal=False)
Definition: translators.py:762
def output_doxygen(self, *args, **kwargs)
Definition: translators.py:426
def writeln(self, *args, **kwargs)
Definition: translators.py:364
def output_table_index_generation_code(self, key, idx)
def subsidiary_file_name(self, output_filename)
Definition: translators.py:328
def lut_size_calculation(self, min, max, step)
def lut_access_code(self, table_index, table_name, i)
def lut_factor(self, idx, include_comma=False, include_type=False)
def output_unary_operator(self, operator, operand, paren)
Definition: translators.py:795
def output_lut_generation(self, only_index=None)
def output_nary_operator(self, operator, operands, paren)
Definition: translators.py:781
def close_block(self, blank_line=True, **kwargs)
Definition: translators.py:885
def output_lut_deletion(self, only_index=None)
def send_main_output_to_subsidiary(self, to_subsidiary=True)
Definition: translators.py:355
string STMT_END
Various language tokens #.
Definition: translators.py:141
def output_binary_operator(self, operator, operands, paren, expr)
Definition: translators.py:802
def output_table_index_generation(self, time_name, nodeset=set())
def _parse_lookup_tables(self, lookup_tables)
def _find_variable(self, defn, pe_done=False)
def _process_ci_elts(self, elt, func, **kwargs)
def __init__(self, doc, options=None)
def _create_var_def(self, content, defn_type)
def validate_metadata(self, assume_valid=False)
def _check_var_def(self, var_elt, var_desc)
def _find_var(self, oxmeta_name, definitions)
def create_dt(self, modifier, comp, units)
def __init__(self, model, force=False)
def _get_special_variable(self, varname, ptype=VarTypes.Unknown)
def get_units_by_name(self, uname)
Definition: pycml.py:2725
def amara_parse_cellml(source, uri=None, prefixes=None)
Definition: pycml.py:191
def description(self, force=False, cellml=False)
Definition: pycml.py:2670
def child_i(elt, i)
Definition: pycml.py:3449
def get_options(args, default_options=None)
def version_comment(note_time=True)
Definition: translators.py:62
def load_model(model_file, options)
def open_output_stream(fname)
Definition: utilities.py:298
def amara_parse(source, uri=None, rules=None, binderobj=None, prefixes=None)
Definition: utilities.py:192
def LOG(facility, level, *args)
Definition: utilities.py:105
def call_if(tf, callable, *args, **kwargs)
Definition: utilities.py:255
def close_output_stream(stream)
Definition: utilities.py:314
def DEBUG(facility, *args)
Definition: utilities.py:95
def validate(self, stream)
Definition: validator.py:296
std::vector< double > d(NPUPPER *NPUPPER)