Nektar++
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
translators.py
Go to the documentation of this file.
1 # We want 1/2==0.5
2 from __future__ import division
3 
4 """Copyright (c) 2005-2016, University of Oxford.
5 All rights reserved.
6 
7 University of Oxford means the Chancellor, Masters and Scholars of the
8 University of Oxford, having an administrative office at Wellington
9 Square, Oxford OX1 2JD, UK.
10 
11 This file is part of Chaste.
12 
13 Redistribution and use in source and binary forms, with or without
14 modification, 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 
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
28 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
30 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 """
35 
36 """
37 This part of PyCml deals with converting CellML models into
38 programming language code, primarily C++ compatible with Chaste, but
39 supporting a few other languages also (and easily extensible).
40 """
41 
42 import optparse #adds options to the command line parsing stuff
43 import os #imports OS functions
44 import re #imports regular expression operations
45 import time #lets you do various time-related operations
46 import sys #imports system functions (e.g. which path we are on)
47 from 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
50 import pycml
51 from pycml import * # Put contents in the local namespace as well
52 import optimize
53 import processors
54 import validator
55 
56 
57 #this sets the version
58 __version__ = "$Revision: 25950 $"[11:-2]
59 
60 
61 #this function generates a version comment
62 def 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?
74 def 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 
88 class TranslationError(RuntimeError):
89  """Error thrown if CellML translation fails."""
90  pass
91 
92 class ConfigurationError(ValueError):
93  """Error thrown if configuration file is invalid."""
94  pass
95 
96 
97 
98 class 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
171  self.indent_level = 0
172  # Character to indent with
173  self.indent_char = ' '
174  # No. of occurrences of indent_char per indent_level
175  self.indent_factor = 4
176  # Whether to use lookup tables where possible
177  self.use_lookup_tables = True
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
229  self.output_top_boilerplate()
230  self.output_mathematics()
231  self.output_bottom_boilerplate()
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)
301  if self.USES_SUBSIDIARY_FILE:
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)
305  if self.USES_SUBSIDIARY_FILE:
307 
308  # Translate
309  if continuation:
310  continuation()
311  else:
313  self.output_mathematics()
316  if self.USES_SUBSIDIARY_FILE:
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
348  self.USES_SUBSIDIARY_FILE = False
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:
382  if not self.USES_SUBSIDIARY_FILE:
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()
542  if self.use_lookup_tables: self.output_lut_deletion()
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 
1220  def contained_table_indices(self, node):
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)
1298  self.output_table_index_generation_code(key, idx)
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)
1312  if self.constrain_table_indices:
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 
1359 class 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 
1394  def add_dt_reference(self):
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
1456  self._get_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
1508  self._get_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 
1807 class ConfigurationStore(object):
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
1829  self.Cm_definitions = []
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_.*']
1837  self.i_ionic_vars = []
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 
1960  def finalize_config(self):
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 
2360  def expose_variables(self):
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 
2489 def 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 
2754 def 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 
2789 def 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")
string STMT_END
Various language tokens #.
Definition: translators.py:141
def scan_for_lookup_tables
Lookup table methods #.
Definition: translators.py:980
def amara_parse_cellml
Definition: pycml.py:191
def calculate_extended_dependencies
Dependency related methods #.
Definition: translators.py:899
def call_if
# Miscellaneous functions # # ...
Definition: utilities.py:255