Nektar++
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
pycml.py
Go to the documentation of this file.
1 
2 # We want 1/2==0.5
3 from __future__ import division
4 
5 """Copyright (c) 2005-2016, University of Oxford.
6 All rights reserved.
7 
8 University of Oxford means the Chancellor, Masters and Scholars of the
9 University of Oxford, having an administrative office at Wellington
10 Square, Oxford OX1 2JD, UK.
11 
12 This file is part of Chaste.
13 
14 Redistribution and use in source and binary forms, with or without
15 modification, are permitted provided that the following conditions are met:
16  * Redistributions of source code must retain the above copyright notice,
17  this list of conditions and the following disclaimer.
18  * Redistributions in binary form must reproduce the above copyright notice,
19  this list of conditions and the following disclaimer in the documentation
20  and/or other materials provided with the distribution.
21  * Neither the name of the University of Oxford nor the names of its
22  contributors may be used to endorse or promote products derived from this
23  software without specific prior written permission.
24 
25 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
29 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
31 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
34 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 """
36 
37 # Initial work on a Python tool for processing CellML files.
38 # Eventual features:
39 # - Featureful & draconian validation
40 # - Apply various automatic optimisations, including PE & LUT
41 # - Easy code generation
42 # - Work around common problems in models, such as 0/0
43 # - As in Memfem, convert from alpha&beta form to tau&inf
44 
45 # This module contains code common to both validation and transformation.
46 
47 # Ideas:
48 # - CellML has the potential for introducing scripting functionality.
49 # This may be a good way of incorporating LUT into the data model.
50 # A special component could represent the table generation, and a
51 # scripted function the lookup.
52 # Alternatively we could add attributes in an extension namespace
53 # to specify LUT parameters (var name, min, max, step).
54 # - Add attributes in an extension namespace to represent binding
55 # time annotations.
56 # - We could do units conversions in a separate processing pass,
57 # adding in extra mathematics to the CellML.
58 
59 # Turn off DeprecationWarnings
60 import warnings
61 warnings.simplefilter('ignore', DeprecationWarning)
62 
63 # Pythonic XML bindings
64 import amara
65 from amara import binderytools as bt
66 
67 from Ft.Xml import SplitQName
68 from xml.dom import Node # For nodeType values
69 
70 import copy
71 import itertools
72 import math
73 import operator
74 import sys
75 import types
76 from cStringIO import StringIO
77 
78 from utilities import *
79 from enum import Enum # Pythonic enums
80 
81 import cellml_metadata # Handle RDF metadata for CellML
82 
83 processors = None
85  """Lazy import."""
86  global processors
87  if processors is None:
88  import processors
89 
90 
91 __version__ = "$Revision: 25949 $"[11:-2]
92 
93 
94 ######################################################################
95 # Logging #
96 ######################################################################
97 import logging
98 
99 # Default config for root logger
100 logging.basicConfig(level=logging.CRITICAL,
101  format="%(name)s: %(levelname)s: %(message)s",
102  stream=sys.stderr)
103 logging.getLogger().handlers[0].setLevel(logging.CRITICAL)
104 
105 # Extra logging levels
106 # This level is a warning according to the spec, but an unrecoverable
107 # condition as far as translation is concerned.
108 logging.WARNING_TRANSLATE_ERROR = logging.WARNING + 5
109 logging.addLevelName(logging.WARNING_TRANSLATE_ERROR, 'WARNING')
110 
111 
112 # We specify some namespace prefixes; others are picked
113 # up automatically. These are the standard namespaces we
114 # expect to see in CellML documents; a warning will be given
115 # if others are found.
116 NSS = {u'm' : u'http://www.w3.org/1998/Math/MathML',
117  u'cml': u'http://www.cellml.org/cellml/1.0#',
118  # Our extensions; URIs will probably change?
119  u'pe': u'https://chaste.comlab.ox.ac.uk/cellml/ns/partial-evaluation#',
120  u'lut': u'https://chaste.comlab.ox.ac.uk/cellml/ns/lookup-tables',
121  u'solver': u'https://chaste.comlab.ox.ac.uk/cellml/ns/solver-info',
122  u'oxmeta': u'https://chaste.comlab.ox.ac.uk/cellml/ns/oxford-metadata#',
123  u'pycml': u'https://chaste.comlab.ox.ac.uk/cellml/ns/pycml#',
124  u'proto': u'https://chaste.cs.ox.ac.uk/nss/protocol/0.1#',
125  # Metadata-related
126  u'cmeta' : u"http://www.cellml.org/metadata/1.0#",
127  u'rdf' : u"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
128  u'dc' : u"http://purl.org/dc/elements/1.1/",
129  u'dcterms': u"http://purl.org/dc/terms/",
130  u'bqs' : u"http://www.cellml.org/bqs/1.0#",
131  u'vCard' : u"http://www.w3.org/2001/vcard-rdf/3.0#",
132  u'cg' : u"http://www.cellml.org/metadata/graphs/1.0#",
133  u'cs' : u"http://www.cellml.org/metadata/simulation/1.0#",
134  u'csub' : u"http://www.cellml.org/metadata/custom_subset/1.0#",
135  u'bqbiol' : u"http://biomodels.net/biology-qualifiers/",
136  # Temporary documentation namespace
137  u'doc' : u"http://cellml.org/tmp-documentation"
138  }
139 
140 
141 # Variable classifications
142 VarTypes = Enum('Unknown', 'Free', 'State', 'MaybeConstant', 'Constant',
143  'Computed', 'Mapped')
144 
145 # Elements in the CellML subset of MathML
146 CELLML_SUBSET_ELTS = frozenset(
147  ['math', 'cn', 'sep', 'ci', 'apply', 'piecewise', 'piece', 'otherwise',
148  'eq', 'neq', 'gt', 'lt', 'geq', 'leq',
149  'plus', 'minus', 'times', 'divide', 'power', 'root', 'abs',
150  'exp', 'ln', 'log', 'floor', 'ceiling', 'factorial',
151  'and', 'or', 'not', 'xor',
152  'diff', 'degree', 'bvar', 'logbase',
153  'sin', 'cos', 'tan', 'sec', 'csc', 'cot',
154  'sinh', 'cosh', 'tanh', 'sech', 'csch', 'coth',
155  'arcsin', 'arccos', 'arctan', 'arcsec', 'arccsc', 'arccot',
156  'arcsinh', 'arccosh', 'arctanh', 'arcsech', 'arccsch', 'arccoth',
157  'true', 'false', 'notanumber', 'pi', 'infinity', 'exponentiale',
158  'semantics', 'annotation', 'annotation-xml'])
159 
160 # Binding times for BTA
161 BINDING_TIMES = Enum('static', 'dynamic')
162 
163 
164 ######################################################################
165 # Helpful utility functions #
166 ######################################################################
167 
169  """
170  Create a specialised binder, given some mappings from element names
171  to python classes, and setting namespace prefixes.
172  """
173  binder = amara.bindery.binder(prefixes=NSS)
174  binder.set_binding_class(NSS[u'cml'], "model", cellml_model)
175  binder.set_binding_class(NSS[u'cml'], "component", cellml_component)
176  binder.set_binding_class(NSS[u'cml'], "variable", cellml_variable)
177  binder.set_binding_class(NSS[u'cml'], "units", cellml_units)
178  binder.set_binding_class(NSS[u'cml'], "unit", cellml_unit)
179  for mathml_elt in ['math', 'degree', 'logbase', 'otherwise',
180  'diff', 'plus', 'minus', 'times', 'divide',
181  'exp', 'ln', 'log', 'abs', 'power', 'root',
182  'leq', 'geq', 'lt', 'gt', 'eq', 'neq',
183  'rem',
184  'ci', 'cn', 'apply', 'piecewise', 'piece',
185  'sin', 'cos', 'tan', 'arcsin', 'arccos', 'arctan']:
186  exec "binder.set_binding_class(NSS[u'm'], '%s', mathml_%s)" % (mathml_elt, mathml_elt)
187  binder.set_binding_class(NSS[u'm'], "and_", mathml_and)
188  binder.set_binding_class(NSS[u'm'], "or_", mathml_or)
189  return binder
190 
191 def amara_parse_cellml(source, uri=None, prefixes=None):
192  """Parse a CellML source with default rules and bindings."""
193  binder = make_xml_binder()
194  rules = [bt.ws_strip_element_rule(u'*')]
195  return amara_parse(source, rules=rules, binderobj=binder)
196 
198  """
199  Check whether elt is safe to make a child, i.e. that it isn't
200  already a child elsewhere.
201  """
202  assert getattr(elt, 'next_elem', None) is None
203  parent = getattr(elt, 'xml_parent', None)
204  if parent:
205  assert elt not in parent.xml_children
206 
207 class element_base(amara.bindery.element_base):
208  """
209  Base element class to allow me to set certain attributes on my instances
210  that are Python objects rather than unicode strings.
211  """
212  def __init__(self):
213  self.xml_attributes = {} # Amara should really do this!
214  super(element_base, self).__init__()
215 
216  def __delattr__(self, key):
217  """
218  Bypass Amara's __delattr__ for attribute names that start with _cml_
219  """
220  if key.startswith('_cml_'):
221  del self.__dict__[key]
222  else:
223  amara.bindery.element_base.__delattr__(self, key)
224 
225  def __setattr__(self, key, value):
226  """
227  Bypass Amara's __setattr__ for attribute names that start with _cml_
228  """
229  if key.startswith('_cml_'):
230  self.__dict__[key] = value
231  else:
232  amara.bindery.element_base.__setattr__(self, key, value)
233 
234  @property
235  def rootNode(self):
236  p = self.parentNode
237  if p:
238  return p.rootNode
239  elif isinstance(self, mathml):
240  raise ValueError('MathML element with no parent!')
241  return self
242 
243  @property
244  def cmeta_id(self):
245  """Get the value of the cmeta:id attribute, or the empty string if not set."""
246  return self.getAttributeNS(NSS['cmeta'], u'id')
247 
248  def xml_remove_child_at(self, index=-1):
249  """
250  Remove child object at a given index
251  index - optional, 0-based index of child to remove (defaults to the last child)
252  """
253  obj = self.xml_children[index]
254  if isinstance(obj, unicode):
255  del self.xml_children[index]
256  else:
257  # Remove references to the object
258  # Probably a slow way to go about this
259  for attr, val in self.__dict__.items():
260  if not (attr.startswith('xml') or
261  attr.startswith('_cml_') or
262  attr in self.xml_ignore_members):
263  next = getattr(val, 'next_elem', None)
264  if val == obj:
265  del self.__dict__[attr]
266  if next: self.__dict__[attr] = next
267  while next:
268  prev, val = val, next
269  next = getattr(val, 'next_elem', None)
270  if val == obj:
271  prev.next_elem = next
272  break
273  del self.xml_children[index]
274 
275  def xml_doc(self):
276  msg = []
277  xml_attrs = []
278  if hasattr(self, 'xml_attributes'):
279  msg.append('Object references based on XML attributes:')
280  for apyname in self.xml_attributes:
281  local, ns = self.xml_attributes[apyname]
282  if ns:
283  source_phrase = " based on '{%s}%s' in XML"%(ns, local)
284  else:
285  source_phrase = " based on '%s' in XML"%(local)
286  msg.append(apyname+source_phrase)
287  xml_attrs.append(apyname)
288  msg.append('Object references based on XML child elements:')
289  for attr, val in self.__dict__.items():
290  if not (attr.startswith('xml') or
291  attr.startswith('_cml_') or
292  attr in self.xml_ignore_members):
293  if attr not in xml_attrs:
294  count = len(list(getattr(self, attr)))
295  if count == 1:
296  count_phrase = " (%s element)"%count
297  else:
298  count_phrase = " (%s elements)"%count
299  local, ns = val.localName, val.namespaceURI
300  if ns:
301  source_phrase = " based on '{%s}%s' in XML"%(ns, local)
302  else:
303  source_phrase = " based on '%s' in XML"%(local)
304  msg.append(attr+count_phrase+source_phrase)
305  return u'\n'.join(msg)
306 
307  @property
308  def xml_properties(self):
309  """
310  Return a dictionary whose keys are Python properties on this
311  object that represent XML attributes and elements, and whose vaues
312  are the corresponding objects (a subset of __dict__)
313  """
314  properties = {}
315  for attr in self.__dict__:
316  if (not (attr.startswith('xml')
317  or attr.startswith('_cml_')
318  or attr in self.xml_ignore_members)):
319  properties[attr] = self.__dict__[attr]
320  return properties
321 
322 # Add some improved/new methods to all bindings
324  def getAttributeNS(self, ns, local, default=u""):
325  """
326  Get the value of an attribute specified by namespace and localname.
327 
328  Optionally can also pass a default value if the attribute
329  doesn't exist (defaults to the empty string).
330  """
331  attrs = getattr(self, 'xml_attributes', {})
332  keys = [ (ns_, SplitQName(qname)[1])
333  for _, (qname, ns_) in attrs.items() ]
334  values = [ unicode(getattr(self, attr))
335  for attr, (qname, ns_) in attrs.items() ]
336  attr_dict = dict(zip(keys, values))
337  return attr_dict.get((ns, local), default)
338 
339  def xml_element_children(self, elt=None):
340  """Return an iterable over child elements of this element."""
341  if elt is None:
342  elt = self
343  for child in elt.xml_children:
344  if getattr(child, 'nodeType', None) == Node.ELEMENT_NODE:
345  yield child
346 
347  def safe_remove_child(self, child, parent=None):
348  """Remove a child element from parent in such a way that it can safely be added elsewhere."""
349  if parent is None: parent = self
350  parent.xml_remove_child(child)
351  child.next_elem = None
352 
353  def replace_child(self, old, new, parent=None):
354  """Replace child old of parent with new."""
355  if parent is None: parent = self
356  parent.xml_insert_after(old, new)
357  self.safe_remove_child(old, parent)
358 
359  import new
360  for method in ['getAttributeNS', 'xml_element_children', 'safe_remove_child', 'replace_child']:
361  meth = new.instancemethod(locals()[method], None, amara.bindery.element_base)
362  setattr(amara.bindery.element_base, method, meth)
363 
365 
366 class comment_base(amara.bindery.comment_base):
367  """An iterable version of comment nodes."""
368  def __init__(self, body=None):
369  amara.bindery.comment_base.__init__(self, body)
370  def __iter__(self):
371  return unitary_iterator(self)
372 
373 ######################################################################
374 # CellML elements #
375 ######################################################################
376 
378  """
379  Specialised class for the model element of a CellML document.
380  Adds methods for collecting and reporting validation errors, etc.
381  """
382 
383  def __init__(self):
384  element_base.__init__(self)
387  self._cml_variables = {}
388  self._cml_components = {}
389  self._cml_units = {}
391  self._cml_units_map = {}
392  # Topologically sorted assignments list
394 
395  def __del__(self):
396  self.clean_up()
397 
398  def clean_up(self):
399  """Try to get the RDF library to clean up nicely."""
400  cellml_metadata.remove_model(self)
401 
402  def get_config(self, config_attr=None):
403  """Get the configuration store if it exists, or an attribute thereof."""
404  config = getattr(self.xml_parent, '_cml_config', None)
405  if config_attr:
406  config = getattr(config, config_attr, None)
407  return config
408 
409  def get_option(self, option_name):
410  """Get the value of a command-line option."""
411  config = getattr(self.xml_parent, '_cml_config', None)
412  return config and getattr(config.options, option_name)
413 
414  def get_component_by_name(self, compname):
415  """Return the component object that has name `compname'."""
416  return self._cml_components[compname]
417 
418  def get_variable_by_name(self, compname, varname):
419  """
420  Return the variable object with name `varname' in component
421  `compname'.
422  """
423  try:
424  var = self._cml_variables[(compname, varname)]
425  except KeyError, e:
426  if compname == u'':
427  if self.component.ignore_component_name:
428  compname = self.component.name
429  else:
430  try:
431  compname, varname = cellml_variable.split_name(varname)
432  except ValueError:
433  raise e
434  var = self._cml_variables[(compname, varname)]
435  else:
436  raise e
437  return var
438 
439  def get_variable_by_oxmeta_name(self, name, throw=True):
440  """
441  Get the unique variable in this model with the given Oxford metadata
442  name annotation.
443 
444  If throw is True, will raise ValueError if there is no such variable,
445  or more than 1 match. If throw is False, returns None in these cases.
446  """
447  vars = cellml_metadata.find_variables(self,
448  ('bqbiol:is', NSS['bqbiol']),
449  ('oxmeta:'+str(name), NSS['oxmeta']))
450  if len(vars) == 1:
451  var = vars[0]
452  elif throw:
453  raise ValueError('"%s" does not name a unique variable (matches: %s)'
454  % (name, str(vars)))
455  else:
456  var = None
457  return var
458 
460  """Return a list of variables annotated with the given ontology term.
461 
462  The annotations have the same form as for oxmeta name annotations (see
463  get_variable_by_oxmeta_name). However, here we are not restricted to
464  namespace, and no check is done on the number of results returned.
465 
466  The given term must be a (prefixed_name, nsuri) tuple.
467  """
468  assert isinstance(term, tuple)
469  assert len(term) == 2
470  named_vars = cellml_metadata.find_variables(self, ('bqbiol:is', NSS['bqbiol']), term)
471  category_vars = cellml_metadata.find_variables(self, ('bqbiol:isVersionOf', NSS['bqbiol']), term)
472  return named_vars + category_vars
473 
474  def get_variable_by_cmeta_id(self, cmeta_id):
475  """
476  Get the unique variable in this model with the given cmeta:id attribute value.
477  """
478  vars = self.xml_xpath(u'cml:component/cml:variable[@cmeta:id="%s"]' % cmeta_id)
479  if len(vars) != 1:
480  raise ValueError('"%s" does not ID a unique variable (matches: %s)'
481  % (cmeta_id, str(vars)))
482  return vars[0]
483 
484  def get_all_variables(self):
485  """Return an iterator over the variables in the model."""
486  for comp in getattr(self, u'component', []):
487  for var in getattr(comp, u'variable', []):
488  yield var
489 
490  def _add_variable(self, var, varname, compname):
491  """Add a new variable to the model."""
492  assert (compname, varname) not in self._cml_variables
493  self._cml_variables[(compname, varname)] = var
494 
495  def _del_variable(self, varname, compname):
496  """Remove a variable from the model."""
497  del self._cml_variables[(compname, varname)]
498 
499  def _add_component(self, comp, special=False):
500  """Add a new component to the model."""
501  if special:
502  comp.xml_parent = self
503  else:
504  self.xml_append(comp)
505  self._cml_components[comp.name] = comp
506 
507  def _del_component(self, comp):
508  """Remove the given component from the model."""
509  self.xml_remove_child(comp)
510  del self._cml_components[comp.name]
511 
512  def validation_error(self, errmsg, level=logging.ERROR):
513  """Log a validation error message.
514 
515  Message should be a unicode string.
516  """
517  self._cml_validation_errors.append(errmsg)
518  logging.getLogger('validator').log(level, errmsg.encode('UTF-8'))
520  """Return the list of all errors found (so far) while validating this model."""
521  return self._cml_validation_errors
522  def validation_warning(self, errmsg, level=logging.WARNING):
523  """Log a validation warning message.
524 
525  Message should be a unicode string.
526  """
527  self._cml_validation_warnings.append(errmsg)
528  logging.getLogger('validator').log(level, errmsg.encode('UTF-8'))
530  """Return the list of all warnings found (so far) while validating this model.
531  """
532  return self._cml_validation_warnings
533  def _report_exception(self, e, show_xml_context):
534  """Report an exception e as a validation error or warning.
535 
536  If show_xml_context is True, display the XML of the context
537  of the exception as well.
538  """
539  e.show_xml_context = show_xml_context
540  if e.warn:
541  self.validation_warning(unicode(e), level=e.level)
542  else:
543  self.validation_error(unicode(e), level=e.level)
544 
545  def validate(self, xml_context=False,
546  invalid_if_warnings=False,
547  warn_on_units_errors=False,
548  check_for_units_conversions=False,
549  assume_valid=False, **ignored_kwargs):
550  """Validate this model.
551 
552  Assumes that RELAX NG validation has been done. Checks rules
553  3.4.2.2, 3.4.3.2, 3.4.3.3, 3.4.5.2, 3.4.5.3, 3.4.5.4, 3.4.6.2, 3.4.6.3, 3.4.6.4,
554  4.4.2.1, 4.4.3.2, 4.4.4, 5.4.1.2, 5.4.2.2, 6.4.2.5, 6.4.3.2, and 6.4.3.3
555  in the CellML 1.0 spec, and performs units checking.
556 
557  Note that if some checks fail, most of the remaining checks
558  will not be performed. Hence when testing a model validate
559  repeatedly until it passes.
560 
561  If xml_context is True, then the failing MathML tree will be
562  displayed with every units error.
563 
564  If check_for_units_conversions is True, then generate a warning if
565  units conversions will be needed.
566 
567  If assume_valid is True then fewer checks will be done - only
568  what is required to set up the data structures needed for model
569  transformation.
570 
571  Returns True iff the model validates.
572  When invalid_if_warnings is True the model will fail to validate
573  if there are any warnings, as well as if there are any errors.
574  """
576 
577  # Rule 5.4.2.2: units definitions may not be circular.
578  # Also checks 5.4.1.2: no duplicate units names.
579  if not assume_valid:
580  for unit in self.get_all_units():
581  self._check_unit_cycles(unit)
582  DEBUG('validator', 'Checked for units cycles')
583  # Check rule 3.4.3.3 too.
585 
586  if not self._cml_validation_errors:
587  self._check_variable_mappings() # This sets up source variable links
588  if not self._cml_validation_errors and not assume_valid:
589  self._check_connection_units(check_for_units_conversions)
590 
591  # Rules 4.4.2.1 and 4.4.3.2: check name references in mathematics
592  if not self._cml_validation_errors:
593  assignment_exprs = self.search_for_assignments()
594  if not assume_valid:
595  for expr in assignment_exprs:
596  self._check_maths_name_references(expr, xml_context)
597 
598  # Rule 4.4.4: mathematical expressions may only modify
599  # variables belonging to the current component.
600  if not self._cml_validation_errors and not assume_valid:
601  self._check_assigned_vars(assignment_exprs, xml_context)
602 
603  # Warn if mathematics outside the CellML subset is used.
604  if not self._cml_validation_errors and not assume_valid:
605  math_elts = self.xml_xpath(self.math_xpath_1 + u' | ' + self.math_xpath_2)
606  self._check_cellml_subset(math_elts)
607 
608  # Classify variables and check for circular equations.
609  # Does a topological sort of all equations in the process.
610  # TODO: Handle reactions properly.
611  if not self._cml_validation_errors:
612  self._classify_variables(assignment_exprs, xml_context)
613  self._order_variables(assignment_exprs, xml_context)
614 
615  # Appendix C.3.6: Equation dimension checking.
616  if not self._cml_validation_errors and (not assume_valid or check_for_units_conversions):
617  self._check_dimensional_consistency(assignment_exprs,
618  xml_context,
619  warn_on_units_errors,
620  check_for_units_conversions)
621 
622  # Warn if unknown namespaces are used, just in case.
623  unknown_nss = set(self.rootNode.xml_namespaces.keys()).difference(set(NSS.values()))
624  if unknown_nss:
625  self.validation_warning(u'Unrecognised namespaces used:\n ' +
626  u'\n '.join(list(unknown_nss)))
627 
628  # Return validation result
629  return not self._cml_validation_errors and \
630  (not invalid_if_warnings or not self._cml_validation_warnings)
631 
633  """Check Rule 6.4.3.2 (4): hierarchies must not be circular.
634 
635  Builds all the hierarchies, and checks for cycles.
636  In the process, we also check the other rules in 6.4.3, and 6.4.2.5.
637  """
638  # First, we find the hierarchies that are defined.
639  hiers = set()
640  rels = []
641  for group in getattr(self, u'group', []):
642  local_hiers = set()
643  for rel in getattr(group, u'relationship_ref', []):
644  rels.append(rel)
645  reln = rel.relationship
646  ns = rel.xml_attributes[u'relationship'][1]
647  name = getattr(rel, u'name', None)
648  hier = (reln, ns, name)
649  if hier in local_hiers:
650  self.validation_error("A group element must not contain two or more relationship_ref"
651  " elements that define a relationship attribute in a common"
652  " namespace with the same value and that have the same name"
653  " attribute value (which may be non-existent) (6.4.2.5)."
654  " Relationship '%s' name '%s' in namespace '%s' is repeated."
655  % (reln, name or '', ns))
656  local_hiers.add(hier)
657  hiers.add(hier)
658  # Now build & check each hierarchy
659  for hier in hiers:
660  self.build_component_hierarchy(hier[0], hier[1], hier[2], rels=rels)
661  DEBUG('validator', 'Checked component hierachies')
662 
664  """Check Rules 3.4.{5,6}: check variable mappings and interfaces are sane."""
665  # First check connection elements and build mappings dict
667  connected_components = set()
668  for connection in getattr(self, u'connection', []):
669  comps = frozenset([connection.map_components.component_1, connection.map_components.component_2])
670  if comps in connected_components:
671  self.validation_error("Each map_components element must map a unique pair of components "
672  "(3.4.5.4). The pair ('%s', '%s') is repeated." % tuple(comps))
673  connected_components.add(comps)
674  self._validate_connection(connection)
675  # Now check for variables that should receive a value but don't
676  for comp in getattr(self, u'component', []):
677  for var in getattr(comp, u'variable', []):
678  for iface in [u'private_interface', u'public_interface']:
679  if getattr(var, iface, u'none') == u'in':
680  try:
681  var.get_source_variable()
682  except TypeError:
683  # No source variable found
684  self.validation_error("Variable '%s' has a %s attribute with value 'in', "
685  "but no component exports a value to that variable."
686  % (var.fullname(), iface))
687  DEBUG('validator', 'Checked variable mappings')
688 
689  def _validate_connection(self, conn):
690  """Validate the given connection element.
691 
692  Check that the given connection object defines valid mappings
693  between variables, according to rules 3.4.5 and 3.4.6.
694  """
695  # Check we are allowed to connect these components
696  try:
697  comp1 = self.get_component_by_name(conn.map_components.component_1)
698  except KeyError:
699  self.validation_error("Connections must be between components defined in the current model "
700  "(3.4.5.2). There is no component '%s'." % conn.map_components.component_1)
701  return
702  try:
703  comp2 = self.get_component_by_name(conn.map_components.component_2)
704  except KeyError:
705  self.validation_error("Connections must be between components defined in the current model "
706  "(3.4.5.3). There is no component '%s'." % conn.map_components.component_2)
707  return
708  if comp1 is comp2:
709  self.validation_error("A connection must link two different components (3.4.5.4). "
710  "The component '%s' is being connected to itself." % comp1.name)
711  return
712  # Get the parent of each component in the encapsulation hierarchy
713  par1, par2 = comp1.parent(), comp2.parent()
714  # The two components must either be siblings (maybe top-level) or parent & child.
715  if not (par1 == comp2 or par2 == comp1 or par1 == par2):
716  self.validation_error(u' '.join([
717  'Connections are only permissible between sibling',
718  'components, or where one is the parent of the other.\n',
719  comp1.name,'and',comp2.name,'are unrelated.']))
720  return
721  # Now check each variable mapping
722  for mapping in conn.map_variables:
723  try:
724  var1 = self.get_variable_by_name(comp1.name, mapping.variable_1)
725  except KeyError:
726  self.validation_error("A variable mapping must be between existing variables (3.4.6.2). "
727  "Variable '%s' doesn't exist in component '%s'."
728  % (mapping.variable_1, comp1.name))
729  continue
730  try:
731  var2 = self.get_variable_by_name(comp2.name, mapping.variable_2)
732  except KeyError:
733  self.validation_error("A variable mapping must be between existing variables (3.4.6.2). "
734  "Variable '%s' doesn't exist in component '%s'."
735  % (mapping.variable_2, comp2.name))
736  continue
737  errm, e = ['Interface mismatch mapping',var1.fullname(),'and',var2.fullname(),':\n'], None
738  if par1 == par2:
739  # Siblings, so should have differing public interfaces
740  if not hasattr(var1, 'public_interface'):
741  e = 'missing public_interface attribute on ' + \
742  var1.fullname() + '.'
743  elif not hasattr(var2, 'public_interface'):
744  e = 'missing public_interface attribute on ' + \
745  var2.fullname() + '.'
746  elif var1.public_interface == var2.public_interface:
747  e = 'public_interface attributes are identical.'
748  else:
749  if var1.public_interface == 'in':
750  var1._set_source_variable(var2)
751  else:
752  var2._set_source_variable(var1)
753  else:
754  if par2 == comp1:
755  # Component 1 is the parent of component 2
756  var1, var2 = var2, var1
757  # Now var2 is in the parent component, and var1 in the child
758  if not hasattr(var1, 'public_interface'):
759  e = var1.fullname()+' missing public_interface.'
760  elif not hasattr(var2, 'private_interface'):
761  e = var2.fullname()+' missing private_interface.'
762  elif var1.public_interface == var2.private_interface:
763  e = 'relevant interfaces have identical values.'
764  else:
765  if var1.public_interface == 'in':
766  var1._set_source_variable(var2)
767  else:
768  var2._set_source_variable(var1)
769  # If there was an error, log it
770  if e:
771  errm.append(e)
772  self.validation_error(u' '.join(errm))
773 
775  """Check rule 3.4.3.3: that the units declared for variables exist."""
776  for var in self.get_all_variables():
777  try:
778  var.get_units()
779  except KeyError:
780  self.validation_error("The value of the units attribute on a variable must be either "
781  "one of the standard units or the name of a unit defined in the "
782  "current component or model (3.4.3.3). The units '%s' on the "
783  "variable '%s' in component '%s' do not exist."
784  % (var.units, var.name, var.component.name))
785 
786  def _check_connection_units(self, check_for_units_conversions=False):
787  """Check that the units of mapped variables are dimensionally consistent.
788 
789  If check_for_units_conversions is True we also warn if they are not equivalent,
790  since much processing software may not be able to handle that case.
791  """
792  for conn in getattr(self, u'connection', []):
793  comp1 = self.get_component_by_name(conn.map_components.component_1)
794  comp2 = self.get_component_by_name(conn.map_components.component_2)
795  for mapping in conn.map_variables:
796  var1 = self.get_variable_by_name(comp1.name, mapping.variable_1)
797  var2 = self.get_variable_by_name(comp2.name, mapping.variable_2)
798  # Check the units
799  u1 = var1.get_units()
800  u2 = var2.get_units()
801  if not u1 == u2:
802  if not u1.dimensionally_equivalent(u2):
803  self.validation_error(u' '.join([
804  var1.fullname(), 'and', var2.fullname(),
805  'are mapped, but have dimensionally inconsistent units.']))
806  elif check_for_units_conversions:
807  self.validation_warning(
808  u' '.join(['Warning: mapping between', var1.fullname(), 'and',
809  var2.fullname(), 'will require a units conversion.']),
810  level=logging.WARNING_TRANSLATE_ERROR)
811 
812  def _check_maths_name_references(self, expr, xml_context=False):
813  """Check rules 4.4.2.1 and 4.4.3.2: name references in mathematics."""
814  if isinstance(expr, mathml_ci):
815  # Check variable exists
816  try:
817  _ = expr.variable
818  except KeyError:
819  self._report_exception(
820  MathsError(expr,
821  "The content of a MathML ci element must match the name of a variable "
822  "in the enclosing component, once whitespace normalisation has been "
823  "performed (4.4.2.1). Variable '%s' does not exist in component '%s'."
824  % (unicode(expr).strip(), expr.component.name)),
825  xml_context)
826  elif isinstance(expr, mathml_cn):
827  # Check units exist
828  try:
829  expr.get_units()
830  except KeyError:
831  self._report_exception(
832  MathsError(expr,
833  "Units on a cn element must be standard or defined in the current "
834  "component or model (4.4.3.2). Units '%s' are not defined in "
835  "component '%s'." % (expr.units, expr.component.name)),
836  xml_context)
837  else:
838  # Recurse
839  for child in expr.xml_element_children():
840  self._check_maths_name_references(child, xml_context)
841 
842  def _check_assigned_vars(self, assignments, xml_context=False):
843  """Check Rule 4.4.4: mathematical expressions may only modify
844  variables belonging to the current component.
845  """
846  for expr in assignments:
847  try:
848  expr.check_assigned_var()
849  except MathsError, e:
850  self._report_exception(e, xml_context)
851  DEBUG('validator', 'Checked variable assignments')
852 
853  def _check_cellml_subset(self, math_elts, root=True):
854  """Warn if MathML outside the CellML subset is used."""
855  for elt in math_elts:
856  if not elt.localName in CELLML_SUBSET_ELTS and \
857  elt.namespaceURI == NSS[u'm']:
858  self.validation_warning(u' '.join([
859  u'MathML element', elt.localName,
860  u'is not in the CellML subset.',
861  u'Some tools may not be able to process it.']))
862  self._check_cellml_subset(self.xml_element_children(elt), False)
863  if root:
864  DEBUG('validator', 'Checked for CellML subset')
865 
866  def _classify_variables(self, assignment_exprs, xml_context=False):
867  """Determine the type of each variable.
868 
869  Note that mapped vars must have already been classified by
870  self._check_variable_mappings, and the RELAX NG schema ensures
871  that a variable cannot be both Mapped and MaybeConstant.
872 
873  Builds the equation dependency graph in the process.
874  """
875  # Classify those vars that might be constants,
876  # i.e. have an initial value assigned.
877  for var in self.get_all_variables():
878  if hasattr(var, u'initial_value'):
879  var._set_type(VarTypes.MaybeConstant)
880  # Now classify by checking usage in mathematics, building
881  # an equation dependency graph in the process.
882  for expr in assignment_exprs:
883  try:
884  expr.classify_variables(root=True)
885  except MathsError, e:
886  self._report_exception(e, xml_context)
887  # Unused vars still classified as MaybeConstant are constants
888  for var in self.get_all_variables():
889  if var.get_type() == VarTypes.MaybeConstant:
890  var._set_type(VarTypes.Constant)
891  DEBUG('validator', 'Classified variables')
892 
893  def _order_variables(self, assignment_exprs, xml_context=False):
894  """Topologically sort the equation dependency graph.
895 
896  This orders all the assignment expressions in the model, to
897  allow procedural code generation. It also checks that equations
898  are not cyclic (we don't support DAEs).
899  """
900  self.clear_assignments() # Ensure we start from a clean slate
901  try:
903  for var in self.get_all_variables():
904  if var.get_colour() == DFS.White:
905  self.topological_sort(var)
906  for expr in assignment_exprs:
907  if expr.get_colour() == DFS.White:
908  self.topological_sort(expr)
909  except MathsError, e:
910  self._report_exception(e, xml_context)
911  DEBUG('validator', 'Topologically sorted variables')
912 
913  math_xpath_1 = u'cml:component/m:math'
914  math_xpath_2 = u'cml:component/cml:reaction/cml:variable_ref/cml:role/m:math'
915  apply_xpath_1 = u'/m:apply[m:eq]'
916  apply_xpath_2 = u'/m:semantics/m:apply[m:eq]'
917 
918  def search_for_assignments(self, comp=None):
919  """Search for assignment expressions in the model's mathematics.
920 
921  If comp is supplied, will only return assignments in that component.
922  """
923  assignments_xpath = u' | '.join([self.math_xpath_1 + self.apply_xpath_1,
924  self.math_xpath_1 + self.apply_xpath_2,
925  self.math_xpath_2 + self.apply_xpath_1,
926  self.math_xpath_2 + self.apply_xpath_2])
927  if comp:
928  assignments_xpath = assignments_xpath.replace(u'component',
929  u'component[@name="%s"]' % comp.name)
930  return self.xml_xpath(assignments_xpath)
931 
932  def build_name_dictionaries(self, rebuild=False):
933  """
934  Create dictionaries mapping names of variables and components to
935  the objects representing them.
936  Dictionary keys for variables will be
937  (component_name, variable_name).
938 
939  If rebuild is True, clear the dictionaries first.
940  """
941  if rebuild:
942  self._cml_variables = {}
943  self._cml_components = {}
944  if not self._cml_components:
945  for comp in getattr(self, u'component', []):
946  if comp.name in self._cml_components:
947  self.validation_error("Component names must be unique within a model (3.4.2.2)."
948  " The name '%s' is repeated." % comp.name)
949  self._cml_components[comp.name] = comp
950  for var in getattr(comp, u'variable', []):
951  key = (comp.name, var.name)
952  if key in self._cml_variables:
953  self.validation_error("Variable names must be unique within a component (3.4.3.2)."
954  " The name '%s' is repeated in component '%s'."
955  % (var.name, comp.name))
956  self._cml_variables[key] = var
957 
958  def build_component_hierarchy(self, relationship, namespace=None, name=None, rels=None):
959  """
960  Create all the parent-child links for the given component hierarchy.
961 
962  relationship gives the type of the hierarchy. If it is not one of the
963  CellML types (i.e. encapsulation or containment) then the namespace URI
964  must be specified. Multiple non-encapsulation hierarchies of the same
965  type can be specified by giving the name argument.
966  """
967  key = (relationship, namespace, name)
968  # Set all components to have no parent or children, under this hierarchy
969  for comp in getattr(self, u'component', []):
970  comp._clear_hierarchy(key)
972  # Find nodes defining this hierarchy
973  if rels is None:
974  rels = self.xml_xpath(u'cml:group/cml:relationship_ref')
975  groups = []
976  for rel in rels:
977  # NB: The first test below relies on there only being one
978  # attr with localname of 'relationship' on each rel.
979  # So let's check this...
980  if hasattr(rel, u'relationship_'):
981  self.validation_error(u' '.join([
982  'relationship_ref element has multiple relationship',
983  'attributes in different namespaces:\n'] +
984  map(lambda qn,ns: '('+qn+','+ns+')',
985  rel.xml_attributes.values())))
986  return
987  if rel.relationship == relationship and \
988  rel.xml_attributes[u'relationship'][1] == namespace and \
989  getattr(rel, u'name', None) == name:
990  # It's in the hierarchy
991  groups.append(rel.xml_parent)
992  # Now build all the parent links for this hierarchy
993  def set_parent(p, crefs):
994  for cref in crefs:
995  # Find component cref refers to
996  try:
997  c = self.get_component_by_name(cref.component)
998  except KeyError:
999  self.validation_error("A component_ref element must reference a component in the current"
1000  " model (6.4.3.3). Component '%s' does not exist." % cref.component)
1001  return
1002  # Set c's parent to p
1003  c._set_parent_component(key, p)
1004  if hasattr(cref, 'component_ref'):
1005  # If we already have children under this hierarchy it's a validation error
1006  if c._has_child_components(key):
1007  self.validation_error("In a given hierarchy, only one component_ref element "
1008  "referencing a given component may contain children (6.4.3.2)."
1009  " Component '%s' has children in multiple locations." % c.name)
1010  # Set parent of c's children to c
1011  set_parent(c, cref.component_ref)
1012  elif p is None and namespace is None:
1013  self.validation_error("Containment and encapsulation relationships must be hierarchical"
1014  " (6.4.3.2). Potentially top-level component '%s' has not been"
1015  " given children." % cref.component)
1016  for group in groups:
1017  set_parent(None, group.component_ref)
1018  if self._cml_validation_errors:
1019  return
1020  # Check for a cycle in the hierarchy (rule 6.4.3.2 (4)).
1021  # Note that since we have already ensured that no component is a parent in more than one location,
1022  # nor is any component a child more than once, so the only possibility for a cycle is if one of
1023  # the components referenced as a child of a group element is also referenced as a (leaf) descendent
1024  # of one of its children. We check for this by following parent links backwards.
1025  def has_cycle(root_comp, cur_comp):
1026  if cur_comp is None:
1027  return False
1028  elif cur_comp is root_comp:
1029  return True
1030  else:
1031  return has_cycle(root_comp, cur_comp.parent(reln_key=key))
1032  for group in groups:
1033  for cref in group.component_ref:
1034  # Find component cref refers to
1035  c = self.get_component_by_name(cref.component)
1036  if has_cycle(c, c.parent(reln_key = key)):
1037  n, ns = name or "", namespace or ""
1038  self.validation_error("The '%s' relationship hierarchy with name '%s' and namespace"
1039  " '%s' has a cycle" % (relationship, n, ns))
1040  return
1041 
1042  def topological_sort(self, node):
1043  """
1044  Do a topological sort of all assignment expressions and variables
1045  in the model.
1046 
1047  node should be an expression or variable object that inherits from
1048  Colourable and has methods get_dependencies, get_component
1049  """
1050  node.set_colour(DFS.Gray)
1051  # Keep track of gray variables, for reporting cycles
1052  if isinstance(node, cellml_variable):
1053  self._cml_sorting_variables_stack.append(node.fullname())
1054  elif node.is_ode():
1055  # This is an expression defining an ODE; the name on the
1056  # stack will look something like d(V)/d(t)
1057  n1, n2 = map(lambda v: v.fullname(), node.assigned_variable())
1058  self._cml_sorting_variables_stack.append(u'd'+n1+u'/d'+n2)
1059  # Visit children in the dependency graph
1060  for dep in node.get_dependencies():
1061  if type(dep) == types.TupleType:
1062  # This is an ODE dependency, so get the defining expression
1063  dep = dep[0].get_ode_dependency(dep[1], node)
1064  if dep.get_colour() == DFS.White:
1065  self.topological_sort(dep)
1066  elif dep.get_colour() == DFS.Gray:
1067  # We have a cyclic dependency
1068  if isinstance(dep, cellml_variable):
1069  i = self._cml_sorting_variables_stack.index(dep.fullname())
1070  elif node.is_ode():
1071  n1, n2 = map(lambda v: v.fullname(),
1072  dep.assigned_variable())
1073  i = self._cml_sorting_variables_stack.index(
1074  u'd'+n1+u'/d'+n2)
1075  else:
1076  # Since any variable always depends on a mathematical
1077  # expression defining it, the only case where the
1078  # expression is gray before the corresponding variable
1079  # (apart from an ODE, dealt with above) is if a tree
1080  # started at an expression. Hence the whole stack
1081  # is in the cycle.
1082  i = 0
1083  varnames = self._cml_sorting_variables_stack[i:]
1084  self.validation_error(u' '.join([
1085  u'There is a cyclic dependency involving the following',
1086  u'variables:', u','.join(varnames)]))
1087  # Finish this node, and add it to the appropriate sorted list
1088  node.set_colour(DFS.Black)
1089  self._add_sorted_assignment(node)
1090  # Pop the gray variables stack
1091  if (isinstance(node, cellml_variable) or node.is_ode()):
1092  self._cml_sorting_variables_stack.pop()
1093  return
1094 
1096  """
1097  During the topological sort, add a finished assignment to the
1098  list. This list can then be executed in order to simulate the
1099  model.
1100 
1101  The element added can either be a MathML expression
1102  representing an assignment, or a CellML variable element,
1103  indicating an assignment due to a variable mapping.
1104  """
1105  self._cml_assignments.append(a)
1106  def _remove_assignment(self, a):
1107  """Remove the given assignment from our list.
1108 
1109  This method is used by the partial evaluator."""
1110  self._cml_assignments.remove(a)
1111  def get_assignments(self):
1112  """
1113  Return a sorted list of all the assignments in the model.
1114 
1115  Assignments can either be instances of cellml_variable, in
1116  which case they represent a variable mapping, or instances of
1117  mathml_apply, representing top-level assignment expressions.
1118  """
1119  return self._cml_assignments
1121  """Clear the assignments list."""
1122  self._cml_assignments = []
1123 
1125  """Perform a binding time analysis on the model's mathematics.
1126 
1127  This requires variables to have been classified and a
1128  topological sort of the mathematics to have been performed.
1129 
1130  Variables and top-level expressions are processed in the order
1131  given by the topological sort, hence only a single pass is
1132  necessary.
1133 
1134  Variables are classified based on their type:
1135  State, Free -> dynamic
1136  Constant -> static
1137  Mapped -> binding time of source variable
1138  Computed -> binding time of defining expression
1139 
1140  Expressions are dealt with by recursively annotating
1141  subexpressions. See code in the MathML classes for details.
1142  """
1143  for item in self.get_assignments():
1144  if isinstance(item, cellml_variable):
1145  # Set binding time based on type
1146  item._get_binding_time()
1147  else:
1148  # Compute binding time recursively
1149  item._get_binding_time()
1150 
1151  def _check_dimensional_consistency(self, assignment_exprs,
1152  xml_context=False,
1153  warn_on_units_errors=False,
1154  check_for_units_conversions=False):
1155  """Appendix C.3.6: Equation dimension checking."""
1157  # Check dimensions
1158  for expr in assignment_exprs:
1159  try:
1160  expr.get_units()
1161  except UnitsError, e:
1162  if warn_on_units_errors:
1163  e.warn = True
1164  e.level = logging.WARNING
1165  self._report_exception(e, xml_context)
1166  # Check if units conversions will be needed
1167  if check_for_units_conversions and not self._cml_validation_errors:
1168  boolean = self.get_units_by_name('cellml:boolean')
1169  for expr in assignment_exprs:
1170  try:
1171  DEBUG('validator', "Checking units in", element_xpath(expr), expr.component.name)
1172  expr._set_in_units(boolean, no_act=True)
1173  except UnitsError:
1174  pass
1175  # Warn if conversions used
1176  if self._cml_conversions_needed:
1177  self.validation_warning('The mathematics in this model require units conversions.',
1178  level=logging.WARNING)
1179  DEBUG('validator', 'Checked units')
1180 
1181  def _check_unit_cycles(self, unit):
1182  """Check for cyclic units definitions.
1183 
1184  We do this by doing a depth-first search from unit.
1185  """
1186  if unit.get_colour() != DFS.White:
1187  # Allow self.validate to call us without colour check
1188  return
1189  unit.set_colour(DFS.Gray)
1190  # Get the object unit is defined in
1191  parent = unit.xml_parent or self
1192  for u in getattr(unit, u'unit', []):
1193  # Explore units that this unit is defined in terms of
1194  try:
1195  v = parent.get_units_by_name(u.units)
1196  except KeyError:
1197  self.validation_error("The value of the units attribute on a unit element must be taken"
1198  " from the dictionary of standard units or be the name of a"
1199  " user-defined unit in the current component or model (5.4.2.2)."
1200  " Units '%s' are not defined." % u.units)
1201  continue
1202  if v.get_colour() == DFS.White:
1203  self._check_unit_cycles(v)
1204  elif v.get_colour() == DFS.Gray:
1205  # We have a cycle
1206  self.validation_error("Units %s and %s are in a cyclic units definition"
1207  % (unit.name, v.name))
1208  unit.set_colour(DFS.Black)
1209 
1211  """Create a dictionary mapping units names to objects, for all units definitions in this element."""
1212  # Standard units
1213  std_units = self._cml_standard_units
1214  def make(name, bases):
1215  return cellml_units.create_new(self, name, bases, standard=True)
1216  # SI base units & dimensionless
1217  base_units = [u'ampere', u'candela', u'dimensionless', u'kelvin',
1218  u'kilogram', u'metre', u'mole', u'second']
1219  base_units.append(u'#FUDGE#') # Used for PE of naughty models
1220  for units in base_units:
1221  std_units[units] = make(units, [])
1222  # Special cellml:boolean units
1223  boolean = make(u'cellml:boolean', [])
1224  std_units[u'cellml:boolean'] = boolean
1225  # Convenience derived units
1226  gram = make('gram', [{'units': 'kilogram', 'multiplier': '0.001'}])
1227  litre = make('litre', [{'multiplier': '1000', 'prefix': 'centi',
1228  'units': 'metre', 'exponent': '3'}])
1229  # SI derived units
1230  radian = make('radian', [{'units': 'metre'},
1231  {'units': 'metre', 'exponent': '-1'}])
1232  steradian = make('steradian', [{'units': 'metre', 'exponent': '2'},
1233  {'units': 'metre', 'exponent': '-2'}])
1234  hertz = make('hertz', [{'units': 'second', 'exponent': '-1'}])
1235  newton = make('newton', [{'units': 'metre'},
1236  {'units': 'kilogram'},
1237  {'units': 'second', 'exponent': '-2'}])
1238  pascal = make('pascal', [{'units': 'newton'},
1239  {'units': 'metre', 'exponent': '-2'}])
1240  joule = make('joule', [{'units': 'newton'},
1241  {'units': 'metre'}])
1242  watt = make('watt', [{'units': 'joule'},
1243  {'units': 'second', 'exponent': '-1'}])
1244  coulomb = make('coulomb', [{'units': 'second'},
1245  {'units': 'ampere'}])
1246  volt = make('volt', [{'units': 'watt'},
1247  {'units': 'ampere', 'exponent': '-1'}])
1248  farad = make('farad', [{'units': 'coulomb'},
1249  {'units': 'volt', 'exponent': '-1'}])
1250  ohm = make('ohm', [{'units': 'volt'},
1251  {'units': 'ampere', 'exponent': '-1'}])
1252  siemens = make('siemens', [{'units': 'ampere'},
1253  {'units': 'volt', 'exponent': '-1'}])
1254  weber = make('weber', [{'units': 'volt'},
1255  {'units': 'second'}])
1256  tesla = make('tesla', [{'units': 'weber'},
1257  {'units': 'metre', 'exponent': '-2'}])
1258  henry = make('henry', [{'units': 'weber'},
1259  {'units': 'ampere', 'exponent': '-1'}])
1260  celsius = make('celsius', [{'units': 'kelvin', 'offset': '-273.15'}])
1261  lumen = make('lumen', [{'units': 'candela'},
1262  {'units': 'steradian'}])
1263  lux = make('lux', [{'units': 'lumen'},
1264  {'units': 'metre', 'exponent': '-2'}])
1265  becquerel = make('becquerel', [{'units': 'second', 'exponent': '-1'}])
1266  gray = make('gray', [{'units': 'joule'},
1267  {'units': 'kilogram', 'exponent': '-1'}])
1268  sievert = make('sievert', [{'units': 'joule'},
1269  {'units': 'kilogram', 'exponent': '-1'}])
1270  katal = make('katal', [{'units': 'second', 'exponent': '-1'},
1271  {'units': 'mole'}])
1272  for units in [becquerel, celsius, coulomb, farad, gram, gray, henry,
1273  hertz, joule, katal, litre, lumen, lux, newton, ohm,
1274  pascal, radian, siemens, sievert, steradian, tesla,
1275  volt, watt, weber]:
1276  std_units[units.name] = units
1277  # American spellings
1278  std_units[u'meter'] = std_units[u'metre']
1279  std_units[u'liter'] = std_units[u'litre']
1280  # User-defined units
1281  model_units = self._cml_units
1282  model_units.update(std_units)
1283  if hasattr(self, u'units'):
1284  for units in self.units:
1285  if units.name in model_units:
1286  self.validation_error("Units names must be unique within the parent component or model,"
1287  " and must not redefine the standard units (5.4.1.2)."
1288  " The units definition named '%s' in the model is a duplicate." % units.name)
1289  model_units[units.name] = units
1290  # Update units hashmap
1291  for u in model_units.itervalues():
1292  self._add_units_obj(u)
1293 
1295  """Get a dictionary mapping the names of the standard CellML units to their definitions."""
1296  if not self._cml_standard_units:
1298  return self._cml_standard_units
1299 
1300  def get_all_units(self):
1301  """Get a list of all units objects, including the standard units."""
1302  if not self._cml_units:
1304  units = self._cml_units.values()
1305  for comp in getattr(self, u'component', []):
1306  units.extend(comp.get_all_units())
1307  return units
1308 
1309  def get_units_by_name(self, uname):
1310  """Return an object representing the element that defines the units named `uname'."""
1311  if not self._cml_units:
1313  try:
1314  return self._cml_units[uname]
1315  except KeyError:
1316  raise KeyError("Units '%s' are not defined in the current component or model." % uname)
1317 
1318  def add_units(self, name, units):
1319  """Add an entry in our units dictionary for units named `name' with element object `units'."""
1320  if not self._cml_units:
1322  assert name not in self._cml_units
1323  self._cml_units[name] = units
1324  self._add_units_obj(units)
1325  return
1326 
1327  def has_units(self, units):
1328  """Test whether a given units definition appears in the model."""
1329  return units in self._cml_units.itervalues()
1330 
1331  def _add_units_obj(self, units):
1332  """Add a units object into the global hashmap."""
1333  if not units.uniquify_tuple in self._cml_units_map:
1334  self._cml_units_map[units.uniquify_tuple] = units
1335  return
1336 
1337  def _get_units_obj(self, units):
1338  """Unique-ify this units object.
1339 
1340  If an object with the same definition already exists, return that.
1341  Otherwise return the given units object.
1342 
1343  'Same definition' is based on the cellml_units.uniquify_tuple
1344  property, which in turn is based partly on the generated name
1345  which would be given to these units, since that really *must*
1346  be unique in generated models.
1347  """
1348  return self._cml_units_map.get(units.uniquify_tuple, units)
1349 
1350  def _is_new_units_obj(self, units):
1351  """Have these units been generated already?
1352 
1353  i.e. is a units object with this definition in our map?
1354  """
1355  return units.uniquify_tuple not in self._cml_units_map
1356 
1358  """Add explicit units conversion mathematics where necessary."""
1360  processors.UnitsConverter(self).add_all_conversions()
1361 
1362  def find_state_vars(self):
1363  """Return a list of the state variable elements in this model."""
1364  state_vars = []
1365  for var in self.get_all_variables():
1366  if var.get_type() == VarTypes.State:
1367  state_vars.append(var)
1368  return state_vars
1369 
1370  def find_free_vars(self):
1371  """Return a list of the free variable elements in this model."""
1372  free_vars = []
1373  for var in self.get_all_variables():
1374  if var.get_type() == VarTypes.Free:
1375  free_vars.append(var)
1376  return free_vars
1377 
1378  def calculate_extended_dependencies(self, nodes, prune=[],
1379  prune_deps=[],
1380  state_vars_depend_on_odes=False,
1381  state_vars_examined=set()):
1382  """Calculate the extended dependencies of the given nodes.
1383 
1384  Recurse into the dependency graph, in order to construct a
1385  set, for each node in nodes, of all the nodes on which it
1386  depends, either directly or indirectly.
1387 
1388  Each node IS included in its own dependency set.
1389 
1390  If prune is specified, it should be a set of nodes for which
1391  we won't include their dependencies or the nodes themselves.
1392  This is useful e.g. for pruning variables required for calculating
1393  a stimulus if the stimulus is being provided by another method.
1394  prune_deps is similar: dependencies of these nodes will be excluded,
1395  but the nodes themselves will be included if asked for.
1396 
1397  If state_vars_depend_on_odes is True, then considers state variables
1398  to depend on the ODE defining them.
1399 
1400  Requires the dependency graph to be acyclic.
1401 
1402  Return the union of all the dependency sets.
1403  """
1404  deps = set()
1405  for node in nodes:
1406  if node in prune or (isinstance(node, mathml_apply) and
1407  isinstance(node.operator(), mathml_eq) and
1408  isinstance(node.eq.lhs, mathml_ci) and
1409  node.eq.lhs.variable in prune):
1410  continue
1411  if type(node) == tuple:
1412  # This is an ODE dependency, so get the defining expression instead.
1413  ode = True
1414  orig_node = node
1415  node = node[0].get_ode_dependency(node[1])
1416  if orig_node in prune_deps:
1417  # Include the defining expression, but skip its dependencies
1418  deps.add(node)
1419  continue
1420  free_var = node.eq.lhs.diff.independent_variable
1421  else:
1422  ode = False
1423  deps.add(node)
1424  if node in prune_deps:
1425  # Skip dependencies of this node
1426  continue
1427  nodedeps = set(node.get_dependencies())
1428  if ode and not node._cml_ode_has_free_var_on_rhs:
1429  # ODEs depend on their independent variable. However,
1430  # when writing out code we don't want to pull the free
1431  # variable in just for this, as the compiler then
1432  # gives us unused variable warnings.
1433  nodedeps.remove(free_var)
1434  if (state_vars_depend_on_odes and isinstance(node, cellml_variable)
1435  and node.get_type() == VarTypes.State
1436  and node not in state_vars_examined):
1437  nodedeps.update(node.get_all_expr_dependencies())
1438  state_vars_examined.add(node)
1439  deps.update(self.calculate_extended_dependencies(nodedeps,
1440  prune=prune,
1441  prune_deps=prune_deps,
1442  state_vars_depend_on_odes=state_vars_depend_on_odes,
1443  state_vars_examined=state_vars_examined))
1444  return deps
1445 
1447  """Determine whether this model is self-excitatory,
1448  i.e. does not require an external stimulus.
1449  """
1450  meta_id = self.cmeta_id
1451  if not meta_id:
1452  return False
1453  property = cellml_metadata.create_rdf_node(('pycml:is-self-excitatory', NSS['pycml']))
1454  source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
1455  return cellml_metadata.get_target(self, source, property) == 'yes'
1456 
1457  def xml(self, stream=None, writer=None, **wargs):
1458  """Serialize back to XML.
1459  If stream is given, output to stream.
1460  If writer is given, use it directly.
1461  If neither a stream nor a writer is given, return the output text
1462  as a Python string (not Unicode) encoded as UTF-8.
1463 
1464  This overrides Amara's method, in order to force declaration of
1465  various namespaces with prefixes on this element, and to ensure
1466  the RDF annotations are up-to-date.
1467 
1468  See base class docs for possible keyword arguments.
1469  """
1470  extra_namespaces = {u'cellml': NSS[u'cml'],
1471  u'pe': NSS[u'pe'],
1472  u'lut': NSS[u'lut']}
1473 
1474  # Update RDF block if necessary
1475  cellml_metadata.update_serialized_rdf(self)
1476 
1477  temp_stream = None
1478  close_document = 0
1479  if not writer:
1480  #Change the default to *not* generating an XML decl
1481  if not wargs.get('omitXmlDeclaration'):
1482  wargs['omitXmlDeclaration'] = u'yes'
1483  if stream:
1484  writer = amara.bindery.create_writer(stream, wargs)
1485  else:
1486  temp_stream = StringIO()
1487  writer = amara.bindery.create_writer(temp_stream, wargs)
1488 
1489  writer.startDocument()
1490  close_document = 1
1491  writer.startElement(self.nodeName, self.namespaceURI,
1492  extraNss=extra_namespaces)
1493  if hasattr(self, 'xml_attributes'):
1494  for apyname in self.xml_attributes:
1495  aqname, ans = self.xml_attributes[apyname]
1496  val = self.__dict__[apyname]
1497  writer.attribute(aqname, val, ans)
1498  for child in self.xml_children:
1499  if isinstance(child, unicode):
1500  writer.text(child)
1501  else:
1502  child.xml(writer=writer)
1503  writer.endElement(self.nodeName, self.namespaceURI)
1504  if close_document:
1505  writer.endDocument()
1506  return temp_stream and temp_stream.getvalue()
1507 
1508 
1510  """
1511  Specialised component class, with additional helper methods.
1512  """
1513 
1514  def __init__(self):
1515  element_base.__init__(self)
1516  self._cml_parents = {}
1517  self._cml_children = {}
1518  self._cml_units = None
1519  self._cml_created_by_pe = False
1520 
1521  @property
1523  """Whether to not include the component name in the full names of contained variables."""
1524  return self._cml_created_by_pe or self.name == u''
1525 
1526 
1527  def parent(self, relationship=u'encapsulation', namespace=None, name=None, reln_key=None):
1528  """Find the parent of this component in the given hierarchy.
1529 
1530  We default to the encapsulation hierarchy.
1531 
1532  relationship gives the type of the hierarchy. If it is not one
1533  of the CellML types (i.e. encapsulation or containment) then
1534  the namespace URI must be specified. Multiple non-encapsulation
1535  hierarchies of the same type can be specified by giving the name
1536  argument.
1537 
1538  Results are cached for efficiency.
1539  """
1540  key = reln_key or (relationship, namespace, name)
1541  if not key in self._cml_parents:
1542  assert(reln_key is None)
1543  self.xml_parent.build_component_hierarchy(relationship, namespace, name)
1544  return self._cml_parents[key]
1545 
1546  def _clear_hierarchy(self, reln_key):
1547  """Unset our parent & children in the given hierarchy."""
1548  self._cml_parents[reln_key] = None
1549  self._cml_children[reln_key] = []
1550 
1551  def _set_parent_component(self, reln_key, parent):
1552  """Set the parent of this component in the relationship hierarchy indexed by reln_key to parent.
1553  Trigger a validation error if we already have a parent in this hierarchy.
1554  Also add ourselves to parent's children.
1555  """
1556  if not reln_key in self._cml_parents or self._cml_parents[reln_key] is None:
1557  # Only set parent if we don't already have one
1558  self._cml_parents[reln_key] = parent
1559  else:
1560  self.xml_parent.validation_error("In a given hierarchy, a component may not be a child more"
1561  " than once (6.4.3.2). Component '%s' has multiple parents."
1562  % self.name)
1563  if not parent is None:
1564  parent._add_child_component(reln_key, self)
1565 
1566  def _add_child_component(self, reln_key, child):
1567  """Add child to our list of children in the relationship hierarchy indexed by reln_key."""
1568  if not reln_key in self._cml_children:
1569  self._cml_children[reln_key] = []
1570  self._cml_children[reln_key].append(child)
1571 
1572  def _has_child_components(self, reln_key):
1573  """Determine whether we have any children in the given relationship hierarchy."""
1574  return self._cml_children.get(reln_key, []) != []
1575 
1576 
1578  """Create a dictionary mapping units names to objects, for all units definitions in this element."""
1579  self._cml_units = {}
1580  for units in getattr(self, u'units', []):
1581  if units.name in self._cml_units:
1582  self.validation_error("Units names must be unique within the parent component (5.4.1.2)."
1583  " The name '%s' in component '%s' is duplicated."
1584  % (units.name, self.name))
1585  try:
1586  if self.xml_parent.get_units_by_name(units.name).standard == u'yes':
1587  self.validation_error("Units definitions must not redefine the standard units (5.4.1.2)."
1588  " The name '%s' in component '%s' is not allowed."
1589  % (units.name, self.name))
1590  except:
1591  pass
1592  self._cml_units[units.name] = units
1593  self.xml_parent._add_units_obj(units)
1594  def get_units_by_name(self, uname):
1595  """Return an object representing the element that defines the units named `uname'."""
1596  if self._cml_units is None:
1598  if uname in self._cml_units:
1599  # Units are defined in this component
1600  return self._cml_units[uname]
1601  else:
1602  # Look up units in model element instead
1603  return self.xml_parent.get_units_by_name(uname)
1604  def add_units(self, name, units):
1605  """Add an entry in our units dictionary for units named `name' with element object `units'."""
1606  if self._cml_units is None:
1608  self._cml_units[name] = units
1609  self.xml_parent._add_units_obj(units)
1610  return
1611 
1612  def get_all_units(self):
1613  """Get a list of all units objects defined in this component."""
1614  if self._cml_units is None:
1616  return self._cml_units.values()
1617 
1618  def get_variable_by_name(self, varname):
1619  """Return the variable object with name `varname' in this component."""
1620  return self.xml_parent.get_variable_by_name(self.name, varname)
1621 
1622  def _add_variable(self, var):
1623  """Add a variable to this component."""
1624  # Add element
1625  self.xml_append(var)
1626  # Add to dictionary
1627  self.xml_parent._add_variable(var, var.name, self.name)
1628  return
1629  def _del_variable(self, var, keep_annotations=False):
1630  """Remove a variable from this component."""
1631  if not keep_annotations:
1632  # Remove metadata about the variable
1633  var.remove_rdf_annotations()
1634  # Remove the element
1635  self.xml_remove_child(var)
1636  # Remove from dictionary
1637  self.xml_parent._del_variable(var.name, self.name)
1638  return
1639 
1640  @staticmethod
1641  def create_new(elt, name):
1642  """Create a new component with the given name."""
1643  new_comp = elt.xml_create_element(u'component', NSS[u'cml'],
1644  attributes={u'name': unicode(name)})
1645  return new_comp
1646 
1647 
1649  """
1650  Class representing CellML <variable> elements.
1651  """
1652  def __init__(self):
1653  super(cellml_variable, self).__init__()
1654  self.clear_dependency_info()
1655  return
1656 
1658  """Clear the type, dependency, etc. information for this variable.
1659 
1660  This allows us to re-run the type & dependency analysis for the model.
1661  """
1662  # The type of this variable is not yet known
1663  self._cml_var_type = VarTypes.Unknown
1664  self._cml_source_var = None
1665  self._cml_value = {}
1666  self._cml_binding_time = None
1667  # Dependency graph edges
1671  self.clear_colour()
1672 
1673  def __hash__(self):
1674  """Hashing function for variables.
1675 
1676  Hash is based on hashing the full name of the variable, as
1677  this must be unique within a model. Unfortunately, when we do
1678  partial evaluation, the full name changes!
1679 
1680  TODO: do we need a hash function?
1681  """
1682  return hash(self.fullname(cellml=True))
1683 
1684  def fullname(self, cellml=False, debug=False):
1685  """
1686  Return the full name of this variable, i.e.
1687  '(component_name,variable_name)'.
1688 
1689  If cellml is given as True, return the name in a form compatible with
1690  the CellML spec instead, i.e. component_name__variable_name, unless
1691  the component has its ignore_component_name property set, in which case
1692  just use variable_name.
1693 
1694  If debug is True, also give information about the variable object.
1695  """
1696  if hasattr(self, 'xml_parent'):
1697  parent_name = self.xml_parent.name
1698  ignore_component = self.component.ignore_component_name
1699  else:
1700  parent_name = '*orphan*'
1701  ignore_component = True
1702  if cellml:
1703  if ignore_component:
1704  vn = self.name
1705  else:
1706  vn = parent_name + u'__' + self.name
1707  else:
1708  vn = u'(' + parent_name + u',' + self.name + u')'
1709  if debug:
1710  vn = '%s@0x%x' % (vn, id(self))
1711  return vn
1712 
1713  @staticmethod
1714  def split_name(varname):
1715  """Split a variable name as given by cellml_variable.fullname into constituent parts.
1716 
1717  Returns a tuple (component name, local variable name). If the component name cannot
1718  be identified, it will be returned as the empty string.
1719  """
1720  try:
1721  if varname[0] == u'(':
1722  cname, vname = varname[1:-1].split(u',')
1723  else:
1724  cname, vname = varname.split(u'__', 1)
1725  except ValueError:
1726  cname, vname = u'', varname
1727  return cname, vname
1728 
1729  @staticmethod
1730  def get_variable_object(model, varname):
1731  """Return the variable object that has name varname.
1732 
1733  This method tries to handle various forms of fully qualified variable name, i.e. names which
1734  include the name of the component the variable occurs in, including those created by
1735  CellMLTranslator.code_name.
1736  """
1737  varname = unicode(varname)
1738  if varname[:4] == 'var_':
1739  varname = varname[4:]
1740  cname, vname = cellml_variable.split_name(varname)
1741  if len(model.component) == 1 and cname == model.component.name:
1742  var = model.component.get_variable_by_name(vname)
1743  else:
1744  try:
1745  var = model.get_variable_by_name(cname, vname)
1746  except KeyError, e:
1747  try:
1748  if cname:
1749  vname = cname + u'__' + vname
1750  var = model.component.get_variable_by_name(vname)
1751  except KeyError:
1752  raise e
1753  return var
1754 
1755  def __str__(self):
1756  return 'cellml_variable' + self.fullname()
1757 
1758  def __repr__(self):
1759  return '<cellml_variable %s @ 0x%x>' % (self.fullname(), id(self))
1760 
1761  def get_component(self):
1762  return self.xml_parent
1763  component = property(get_component)
1764 
1765  @property
1766  def model(self):
1767  return self.component.xml_parent
1768 
1769  def get_units(self):
1770  """Get the cellml_units object giving this variable's units."""
1771  return self.component.get_units_by_name(self.units)
1772 
1773  def _add_dependency(self, dep):
1774  """Add a dependency of this variable.
1775 
1776  This could be an expression defining it, or a variable it's mapped from.
1777  Triggers a validation error if we already have another dependency,
1778  since a variable can't be defined in more than one way.
1779  """
1780  if self._cml_depends_on:
1781  if not dep in self._cml_depends_on:
1782  # Multiple dependencies. TODO: Give more info.
1783  raise MathsError(dep, u' '.join([
1784  u'The variable',self.fullname(),
1785  u'gets its value from multiple locations.']))
1786  else:
1787  self._cml_depends_on.append(dep)
1788  return
1789 
1790  def get_dependencies(self):
1791  """
1792  Return the list of things this variable depends on.
1793  """
1794  return self._cml_depends_on
1795 
1796  def _add_ode_dependency(self, independent_var, expr):
1797  """Add a dependency of this variable as the dependent variable in an ODE.
1798 
1799  independent_var is the corresponding independent variable, and expr is the
1800  expression defining the ODE.
1801  Triggers a validation error if the same ODE is defined by multiple expressions.
1802  """
1803  if independent_var in self._cml_depends_on_ode:
1804  if self._cml_depends_on_ode[independent_var] != expr:
1805  # Multiple definitions. TODO: Give more info.
1806  raise MathsError(expr, u''.join([
1807  u'There are multiple definitions of the ODE d',self.fullname(),
1808  u'/d',independent_var.fullname()]))
1809  else:
1810  self._cml_depends_on_ode[independent_var] = expr
1811  return
1812 
1813  def _update_ode_dependency(self, free_var, defn):
1814  """Update an ODE dependency due to partial evaluation.
1815 
1816  When the PE processes the LHS of a derivative equation, it alters the independent
1817  variable to reference its source directly. If this new source wasn't originally
1818  our independent variable's source (e.g. due to a units conversion expression) then
1819  this will break lookups of the ODE via _get_ode_dependency, so we need to update
1820  the reference.
1821  """
1822  if self.get_type() == VarTypes.Mapped:
1823  return self.get_source_variable()._update_ode_dependency(free_var, defn)
1824  self._cml_depends_on_ode.clear()
1825  self._cml_depends_on_ode[free_var] = defn
1826 
1827  def get_ode_dependency(self, independent_var, context=None):
1828  """
1829  Return the expression defining the ODE giving the derivative of this
1830  variable w.r.t. independent_var.
1831  Triggers a validation error if the ODE has not been defined.
1832  """
1833  if self.get_type() == VarTypes.Mapped:
1834  return self.get_source_variable().get_ode_dependency(independent_var, context=context)
1835  free_vars = self._cml_depends_on_ode.keys()
1836  free_vars = dict(zip(map(lambda v: v.get_source_variable(recurse=True), free_vars),
1837  free_vars))
1838  independent_src = independent_var.get_source_variable(recurse=True)
1839  if not independent_src in free_vars:
1840  raise MathsError(context or self, u''.join([
1841  u'The ODE d',self.fullname(),u'/d',independent_var.fullname(),
1842  u'is used but not defined.']))
1843  return self._cml_depends_on_ode[free_vars[independent_src]]
1844 
1846  """Return all expressions this variable depends on, either directly or as an ODE."""
1847  deps = filter(lambda d: isinstance(d, mathml_apply), self._cml_depends_on)
1848  deps.extend(self._cml_depends_on_ode.values())
1849  return deps
1850 
1851  def _set_source_variable(self, src_var):
1852  """Set this to be a mapped variable which imports its value from src_var.
1853  A validation error is generated if we are already mapped.
1854  """
1855  if not self._cml_source_var is None:
1856  # Mapping already exists
1857  model = self.model
1858  debug = model.get_option('debug')
1859  model.validation_error(u' '.join([
1860  'A variable with interface "in" may only obtain its',
1861  'value from one location.\nBoth',
1862  self._cml_source_var.fullname(debug=debug), 'and',
1863  src_var.fullname(debug=debug), 'are exported to', self.fullname(debug=debug)]))
1864  else:
1865  self._cml_source_var = src_var
1866  self._set_type(VarTypes.Mapped)
1867  self._add_dependency(src_var)
1868  return
1869 
1870  def get_source_variable(self, recurse=False):
1871  """
1872  Assuming this variable imports its value, return the variable
1873  from which we obtain our value.
1874  If our value is determined within this component, raise a
1875  TypeError.
1876 
1877  If recurse is set to True, recursively follow mappings until
1878  a non-mapped variable is found.
1879  """
1880  if self._cml_source_var is None:
1881  if recurse:
1882  src = self
1883  else:
1884  raise TypeError(u' '.join([
1885  'Variable', self.fullname(), u'is not a mapped variable',
1886  '(i.e. does not obtain its value from another component).'
1887  ]))
1888  else:
1889  src = self._cml_source_var
1890  if recurse:
1891  src = src.get_source_variable(recurse=True)
1892  return src
1893 
1894  def _set_type(self, var_type, _orig=None):
1895  """Update the type of this variable.
1896 
1897  The caller should check that the update makes sense.
1898 
1899  If this variable already has type Mapped, then update the type of
1900  our source variable, instead.
1901  """
1902  # If this is becoming a state variable, increment its usage count
1903  if var_type is VarTypes.State and not self._cml_var_type is VarTypes.State:
1904  self._cml_usage_count += 1
1905  if self._cml_var_type == VarTypes.Mapped and not _orig is self:
1906  # Guard against infinite loops, since we haven't done a cyclic dependency check yet
1907  if _orig is None: _orig = self
1908  self.get_source_variable()._set_type(var_type, _orig=_orig)
1909  else:
1910  self._cml_var_type = var_type
1911  return
1912 
1913  def get_type(self, follow_maps=False):
1914  """Return the type of this variable.
1915 
1916  If follow_maps is True and the value of this variable is imported
1917  from another component, then return the type of the variable we
1918  get our value from instead.
1919  """
1920  if follow_maps and self._cml_var_type == VarTypes.Mapped:
1921  return self.get_source_variable().get_type(follow_maps=True)
1922  return self._cml_var_type
1923 
1924  def _used(self):
1925  """Note this variable as being used in an expression.
1926 
1927  Keep track of the usage count.
1928  If this is a mapped variable, note its source as being used,
1929  as well.
1930 
1931  Note that if a variable is used in 2 branches of a conditional
1932  then this counts as 2 uses.
1933  """
1934  self._cml_usage_count += 1
1935  if self._cml_var_type == VarTypes.MaybeConstant:
1936  self._cml_var_type = VarTypes.Constant
1937  elif self._cml_var_type == VarTypes.Mapped:
1938  self.get_source_variable()._used()
1939  return
1940 
1941  def get_usage_count(self):
1942  """
1943  Return the number of times this variable is used in an expression.
1944  """
1945  return self._cml_usage_count
1946 
1947  def _decrement_usage_count(self, follow_maps=True):
1948  """Decrement our usage count."""
1949  DEBUG('partial-evaluator', "Dec usage for", self.fullname())
1950  assert self._cml_usage_count > 0
1951  self._cml_usage_count -= 1
1952  if follow_maps and self._cml_var_type == VarTypes.Mapped:
1954  # Note in the model if a usage count has decreased to 1, in
1955  # order to repeat the partial evaluation loop.
1956  if self._cml_usage_count == 1:
1957  model = self.xml_parent.xml_parent
1958  model._pe_repeat = u'yes'
1959  return
1960 
1961  def add_rdf_annotation(self, property, target, allow_dup=False):
1962  """Add an RDF annotation about this variable.
1963 
1964  property must be a tuple (qname, namespace_uri).
1965  target may either be a tuple as above, or a unicode string, in which
1966  case it is interpreted as a literal RDF node.
1967 
1968  If the variable does not already have a cmeta:id, one will be created
1969  for it with value self.fullname(cellml=True).
1970 
1971  The actual RDF will be added to the main RDF block in the model, which
1972  will be created if it does not exist. Any existing annotations of this
1973  variable with the same property will be removed, unless allow_dup=True.
1974  """
1975  meta_id = self.cmeta_id
1976  if not meta_id:
1977  # Create ID for this variable, so we can refer to it in RDF
1978  meta_id = cellml_metadata.create_unique_id(self.model, unicode(self.fullname(cellml=True)))
1979  self.xml_set_attribute((u'cmeta:id', NSS['cmeta']), meta_id)
1980  property = cellml_metadata.create_rdf_node(property)
1981  target = cellml_metadata.create_rdf_node(target)
1982  source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
1983  if allow_dup:
1984  cellml_metadata.add_statement(self.model, source, property, target)
1985  else:
1986  cellml_metadata.replace_statement(self.model, source, property, target)
1987 
1988  def get_rdf_annotation(self, property):
1989  """Get an RDF annotation about this variable.
1990 
1991  property must be a tuple (qname, namespace_uri).
1992 
1993  Will return the unique annotation found with source being this variable's id,
1994  and the given property. If no annotation is found (or if the variable does
1995  not have a cmeta:id), returns None. Throws an error if more than one
1996  annotation is found.
1997  """
1998  meta_id = self.cmeta_id
1999  if not meta_id:
2000  return None
2001  property = cellml_metadata.create_rdf_node(property)
2002  source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
2003  return cellml_metadata.get_target(self.model, source, property)
2004 
2005  def get_rdf_annotations(self, property):
2006  """Get all RDF annotations about this variable that use the given property.
2007 
2008  property must be a tuple (qname, namespace_uri).
2009 
2010  Will return all annotations found with source being this variable's id,
2011  and the given property. If no annotation is found (or if the variable does
2012  not have a cmeta:id), returns the empty list
2013  """
2014  meta_id = self.cmeta_id
2015  if not meta_id:
2016  return []
2017  property = cellml_metadata.create_rdf_node(property)
2018  source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
2019  return cellml_metadata.get_targets(self.model, source, property)
2020 
2021  def remove_rdf_annotations(self, property=None):
2022  """Remove all RDF annotations about this variable.
2023 
2024  If property is given, only remove annotations with the given property.
2025  """
2026  meta_id = self.cmeta_id
2027  if meta_id:
2028  DEBUG('cellml-metadata', "Removing RDF annotations for", self, "with id", meta_id)
2029  source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
2030  if property:
2031  property = cellml_metadata.create_rdf_node(property)
2032  cellml_metadata.remove_statements(self.model, source, property, None)
2033 
2034  def set_rdf_annotation_from_boolean(self, property, is_yes):
2035  """Set an RDF annotation as 'yes' or 'no' depending on a boolean value."""
2036  if is_yes:
2037  val = 'yes'
2038  else:
2039  val = 'no'
2040  self.add_rdf_annotation(property, val)
2041 
2042  def _set_binding_time(self, bt, temporary=False):
2043  """Set the binding time of this variable.
2044 
2045  Options are members of the BINDING_TIMES Enum.
2046 
2047  If temporary is True, then we're temporarily overriding the normal setting,
2048  so save any existing annotation for later replacement.
2049  """
2050  #print "set bt", self, bt, temporary
2051  assert bt in BINDING_TIMES
2052  if temporary and not hasattr(self, '_cml_saved_bt'):
2053  self._cml_saved_bt = self.get_rdf_annotation(('pe:binding_time', NSS[u'pe']))
2054  self.add_rdf_annotation(('pe:binding_time', NSS[u'pe']), str(bt))
2055  self._cml_binding_time = bt
2056  return
2057 
2058  def _unset_binding_time(self, only_temporary=False):
2059  """Unset any stored binding time.
2060 
2061  If the stored BT was a temporary setting, replace it with the original value.
2062  """
2063  #print "unset bt", self, only_temporary
2064  self._cml_binding_time = None
2065  if hasattr(self, '_cml_saved_bt'):
2066  if self._cml_saved_bt:
2067  self._set_binding_time(getattr(BINDING_TIMES, self._cml_saved_bt))
2068  else:
2069  self.remove_rdf_annotations(('pe:binding_time', NSS[u'pe']))
2070  del self._cml_saved_bt
2071  elif not only_temporary:
2072  self.remove_rdf_annotations(('pe:binding_time', NSS[u'pe']))
2073 
2075  """Return the binding time of this variable, as a member of
2076  the BINDING_TIMES Enum.
2077 
2078  This method will (try to) compute & cache the binding time if
2079  necessary.
2080 
2081  Variables are classified based on their type:
2082  State, Free -> dynamic
2083  Constant -> static
2084  Mapped -> binding time of source variable
2085  Computed -> binding time of defining expression
2086  """
2087  if self._cml_binding_time is None:
2088  # Check for an annotation setting the BT
2089  bt_annotation = self.get_rdf_annotation(('pe:binding_time', NSS[u'pe']))
2090  if bt_annotation:
2091  bt = getattr(BINDING_TIMES, bt_annotation)
2092  DEBUG('partial-evaluator', "BT var", self.fullname(), "is annotated as", bt)
2093  elif self.pe_keep:
2094  # This variable must appear in the specialised model
2095  bt = BINDING_TIMES.dynamic
2096  DEBUG('partial-evaluator', "BT var", self.fullname(), "is kept")
2097  else:
2098  # Compute BT based on our type
2099  t = self.get_type()
2100  DEBUG('partial-evaluator', "BT var", self.fullname(), "type", str(t))
2101  if t in [VarTypes.State, VarTypes.Free, VarTypes.Unknown]:
2102  bt = BINDING_TIMES.dynamic
2103  elif t == VarTypes.Constant:
2104  bt = BINDING_TIMES.static
2105  elif t == VarTypes.Mapped:
2107  elif t == VarTypes.Computed:
2108  bt = self._cml_depends_on[0]._get_binding_time()
2109  else:
2110  raise TypeError("Unexpected variable type " + str(t) +
2111  " of variable " + self.fullname() +
2112  " in BTA.")
2113  DEBUG('partial-evaluator', "BT var", self.fullname(), "is", bt)
2114  self._set_binding_time(bt)
2115  else:
2116  bt = self._cml_binding_time
2117  return bt
2118 
2119  def is_statically_const(self, ignore_annotations=False):
2120  """Determine loosely if this variable is considered constant.
2121 
2122  Checks if we're Constant, or Computed with a static binding time (or
2123  of unknown type).
2124 
2125  If ignore_annotations is True, will ignore cached binding time values and
2126  pe:keep annotations. It instead finds all variables we depend on, directly or
2127  indirectly, and gives a dynamic result iff any is a state or free variable.
2128  """
2129  result = False
2130  t = self.get_type()
2131  if t in [VarTypes.Constant, VarTypes.Unknown]:
2132  result = True
2133  elif t == VarTypes.Computed:
2134  if ignore_annotations:
2135  dependencies = self.model.calculate_extended_dependencies([self])
2136  result = True
2137  for node in dependencies:
2138  if isinstance(node, cellml_variable) and node.get_type() in [VarTypes.State, VarTypes.Free]:
2139  result = False
2140  break
2141  else:
2142  result = self._get_binding_time() == BINDING_TIMES.static
2143  elif t == VarTypes.Mapped:
2144  result = self.get_source_variable().is_statically_const(ignore_annotations)
2145  return result
2146 
2147  def set_value(self, value, ode=None, follow_maps=True):
2148  """Set the value of this variable.
2149 
2150  Expects a floating point or boolean value.
2151 
2152  If ode is given, it should be an instance of cellml_variable.
2153  In this case, we're setting the value of d(self)/d(ode).
2154 
2155  If this is a mapped variable, assign the value to its source
2156  variable instead, unless follow_maps is set to False
2157  """
2158  #print "set_value", self, ode, value
2159  if follow_maps and self.get_type() == VarTypes.Mapped:
2160  self.get_source_variable().set_value(value, ode=ode)
2161  else:
2162  assert type(value) in [types.FloatType, types.BooleanType]
2163  self._cml_value[ode] = float(value)
2164  return
2165  def unset_values(self):
2166  """Unset all values for this variable set with set_value."""
2167  #print "unset_values", self
2168  if self.get_type() == VarTypes.Mapped:
2170  self._cml_value.clear()
2171  def get_value(self, ode=None):
2172  """Return the value of this variable.
2173 
2174  If a value has been set with set_value(), return that.
2175  Otherwise, the behaviour depends on the type of this variable.
2176  If it is Mapped, return the value of the source variable.
2177  If it is Constant or State, return its initial value.
2178  Otherwise, raise an EvaluationError.
2179 
2180  If ode is given, it should be an instance of cellml_variable.
2181  In this case, we're getting the value of d(self)/d(ode).
2182  """
2183  # TODO: Might want to alter the handling of initial value for an ODE?
2184  if ode in self._cml_value:
2185  val = self._cml_value[ode]
2186  elif self.get_type() == VarTypes.Mapped:
2187  val = self.get_source_variable().get_value(ode=ode)
2188  elif ode is None and self.get_type() in [VarTypes.Unknown,
2189  VarTypes.State, VarTypes.Constant]:
2190  if hasattr(self, 'initial_value'):
2191  val = float(self.initial_value)
2192  else:
2193  raise EvaluationError("Variable " + self.fullname() + " has no initial value set.")
2194  elif self.get_type() == VarTypes.Computed and self._get_binding_time() == BINDING_TIMES.static:
2195  # Evaluate the defining expression
2196  val = self._cml_depends_on[0].evaluate()
2197  else:
2198  raise EvaluationError("Unable to find a suitable value for variable " + self.fullname())
2199  return val
2200 
2201  @property
2203  """Whether this variable is a parameter that should be modifiable at run-time."""
2204  return (self.get_type() == VarTypes.Constant and
2205  self.get_rdf_annotation(('pycml:modifiable-parameter', NSS['pycml'])) == 'yes')
2206  def set_is_modifiable_parameter(self, is_param):
2207  """Set method for the is_modifiable_parameter property.
2208 
2209  We need a separate method for this to bypass Amara's property setting checks.
2210  """
2211  if is_param and self.get_type() != VarTypes.Constant:
2212  raise ValueError("A non-constant variable (%s) cannot be set as a parameter" % (self.fullname(),))
2213  self.set_rdf_annotation_from_boolean(('pycml:modifiable-parameter', NSS[u'pycml']), is_param)
2214 
2215  @property
2217  """Whether this variable should be included in reports of derived quantities."""
2218  return self.get_rdf_annotation(('pycml:derived-quantity', NSS['pycml'])) == 'yes'
2219  def set_is_derived_quantity(self, is_dq):
2220  """Set method for the is_derived_quantity property.
2221 
2222  We need a separate method for this to bypass Amara's property setting checks.
2223  """
2224  self.set_rdf_annotation_from_boolean(('pycml:derived-quantity', NSS[u'pycml']), is_dq)
2225 
2226  @property
2228  """Whether a protocol has requested this variable as a model output."""
2229  return self.get_rdf_annotation(('pycml:output-variable', NSS['pycml'])) == 'yes'
2230  def set_is_output_variable(self, is_ov):
2231  """Set method for the is_output_variable property.
2232 
2233  We need a separate method for this to bypass Amara's property setting checks.
2234  """
2235  self.set_rdf_annotation_from_boolean(('pycml:output-variable', NSS[u'pycml']), is_ov)
2236 
2237  @property
2238  def pe_keep(self):
2239  """Whether PE should retain this variable in the specialised model."""
2240  return (self.get_rdf_annotation(('pe:keep', NSS[u'pe'])) == 'yes' or
2241  self.is_modifiable_parameter or
2242  self.is_derived_quantity or
2243  self.is_output_variable)
2244  def set_pe_keep(self, keep):
2245  """Set method for the pe_keep property.
2246 
2247  We need a separate method for this to bypass Amara's property setting checks.
2248  """
2249  self.set_rdf_annotation_from_boolean(('pe:keep', NSS[u'pe']), keep)
2250 
2251  @property
2252  def oxmeta_name(self):
2253  """The canonical name of this variable, as given by Oxford metadata.
2254 
2255  Returns the empty string if no annotation is given.
2256  """
2257  for annotation in self.get_rdf_annotations(('bqbiol:is', NSS['bqbiol'])):
2258  name = cellml_metadata.namespace_member(annotation, NSS['oxmeta'], wrong_ns_ok=True)
2259  if name:
2260  return name
2261  # No suitable annotation found
2262  return ""
2263  def set_oxmeta_name(self, name):
2264  """Set method for the oxmeta_name property.
2265 
2266  Sets a bqbiol:is RDF annotation with the name.
2267 
2268  We need a separate method for this to bypass Amara's property setting checks.
2269  """
2270  self.add_rdf_annotation(('bqbiol:is', NSS['bqbiol']), ('oxmeta:'+name, NSS['oxmeta']))
2271 
2272  def _reduce(self, update_usage=False):
2273  """Reduce this dynamic variable that is being kept.
2274 
2275  If it has a static source, then turn it into a constant.
2276  Otherwise make it depend directly on an appropriate source:
2277  either one of its source variables that is also being kept,
2278  or the ultimate defining expression.
2279 
2280  If update_usage is True then this variable is used in an equation,
2281  so reduce the usage of any source variables it no longer depends on.
2282  """
2283  assert self.pe_keep
2284  src = self.get_source_variable(recurse=True)
2285  if src._get_binding_time() is BINDING_TIMES.static:
2286  # Become a constant
2287  self._cml_var_type = VarTypes.Constant
2288  # Set our value
2289  value = unicode(src.get_value())
2290  self.initial_value = value
2291  # Fix up usage counts
2292  if update_usage:
2294  # We now have no dependencies
2295  self._cml_depends_on = []
2296  self._cml_source_var = None
2297  elif self.get_type() == VarTypes.Mapped:
2298  # Manually recurse down maps to find a suitable source
2299  src = self.get_source_variable()
2300  while not src.pe_keep and src.get_type() == VarTypes.Mapped and src.get_usage_count() == 1:
2301  src = src.get_source_variable()
2302  if src.pe_keep or src.get_usage_count() > 1:
2303  # Depend directly on src
2304  self._cml_depends_on = [src]
2305  self._cml_source_var = src
2306  # Fix up usage counts
2307  if update_usage:
2308  src._used()
2310  else: # src.get_type() != VarTypes.Mapped
2311  # This variable is the only reference to the ultimate defining
2312  # expression, so become computed.
2313  self._cml_var_type = VarTypes.Computed
2314  defn = src.get_dependencies()[0]
2315  assert isinstance(defn, mathml_apply)
2316  ## Move the definition to this component
2317  #defn._unset_cached_links()
2318  #defn.xml_parent.xml_remove_child(defn)
2319  #self.component.math.xml_append(defn)
2320  # Schedule the LHS of the defining expression for update
2321  defn._cml_assigns_to = self
2322  defn._pe_process = u'retarget'
2323  # Fix up usage counts
2324  if update_usage:
2326  # Depend directly on the assignment expression
2327  self._cml_depends_on = [defn]
2328  self._cml_source_var = None
2329 
2330  @staticmethod
2331  def create_new(elt, name, units, id=None, initial_value=None, interfaces={}):
2332  """Create a new <variable> element with the given name and units.
2333 
2334  Optionally id, initial_value, and interfaces may also be given.
2335 
2336  elt may be any existing XML element.
2337  """
2338  attrs = {(u'units', None): unicode(units),
2339  (u'name', None): unicode(name)}
2340  if id:
2341  attrs[(u'cmeta:id', NSS[u'cmeta'])] = unicode(id)
2342  if initial_value is not None and initial_value != u'':
2343  attrs[(u'initial_value', None)] = unicode(initial_value)
2344  for iface, val in interfaces.items():
2345  attrs[(iface + u'_interface', None)] = unicode(val)
2346  new_elt = elt.xml_create_element(u'variable', NSS[u'cml'], attributes=attrs)
2347  return new_elt
2348 
2349 class UnitsSet(set):
2350  """A set of cellml_units objects.
2351 
2352  This class behaves like a normal set, but also has additional
2353  methods for operations specific to sets of <units> objects:
2354  simplify - allow for multiplication of sets of units
2355  dimensionally_equivalent - compare 2 sets of units for dimensional equivalence
2356  description - describe the units in this set
2357 
2358  All units in the set (normally) must be dimensionally equivalent. The exception is when dealing with
2359  Functional Curation protocols which can defined units conversion rules for non-scaling cases. We can
2360  then have sets of alternative units in different dimensions, and the get_consistent_set method helps
2361  with selecting from these.
2362  """
2363  def __new__(cls, iterable=[], expression=None):
2364  """
2365  Work around annoyance in set implementation of python 2.4.2c1 and on.
2366  setobject.c checks for keyword arguments in its __new__ instead of its
2367  __init__, so we get an error
2368  'TypeError: set() does not take keyword arguments' if we don't do this.
2369  """
2370  return super(UnitsSet, cls).__new__(cls, iterable)
2371 
2372  def __init__(self, iterable=[], expression=None):
2373  super(UnitsSet, self).__init__(iterable)
2374  self._expression = expression
2375  self._sources = {}
2376  return
2377 
2378  def copy(self):
2379  """Do a shallow copy of this UnitsSet."""
2380  new_set = super(UnitsSet, self).copy()
2381  new_set._expression = self._expression
2382  new_set._sources = {}
2383  for units, src_list in self._sources.iteritems():
2384  new_set._sources[units] = copy.copy(src_list)
2385  return new_set
2386 
2387  def get_consistent_set(self, desired_units):
2388  """Extract a subset of the units in this set that are dimensionally equivalent.
2389 
2390  When dealing with potential non-scaling conversions, an expression may have potential units that are
2391  not within the same dimension. However, when deciding on the units for the expression most operations
2392  need to handle a set of options that *are* within the same dimension, and this operation supports that.
2393 
2394  Given a cellml_units object for the desired units of the expression, this method first tries to create
2395  a UnitsSet containing just our members in the same dimension. If we have no members in the desired
2396  dimension, then we check that all members of this set are in the same dimension, and return the set
2397  itself - there should never be more than 2 different dimensions to choose from (I hope!).
2398  """
2399  new_set = UnitsSet([], expression=self._expression)
2400  for units in self:
2401  if units.dimensionally_equivalent(desired_units):
2402  new_set.add(units)
2403  new_set._sources[units] = copy.copy(self._sources[units])
2404  if not new_set:
2405  rep_u = self.extract()
2406  for units in self:
2407  if not units.dimensionally_equivalent(rep_u):
2408  raise ValueError("Unexpected dimensional variation in UnitsSet; " + rep_u.description() + " and " + units.description()
2409  + " do not match.")
2410  new_set = self
2411  return new_set
2412 
2413  def equals(self, other):
2414  """Test whether the units in the set are equal to those in another set."""
2415  try:
2416  equal = self.extract(check_equality=True).equals(other.extract(check_equality=True))
2417  except ValueError:
2418  equal = False
2419  return equal
2420 
2421  def extract(self, check_equality=False):
2422  """Extract a representative element from this set.
2423 
2424  This is intended to be used to get the cellml_units object from a singleton set.
2425 
2426  If check_equality is True, check that all members of this set have the same multiplicative factor.
2427  """
2428  representative = iter(self).next()
2429  if check_equality:
2430  for u in self:
2431  if not u._rel_error_ok(u.expand().get_multiplicative_factor(),
2432  representative.expand().get_multiplicative_factor(),
2433  1e-6):
2434  raise ValueError("UnitsSet equality check failed")
2435  if u.is_simple() and not u._rel_error_ok(u.expand().get_offset(),
2436  representative.expand().get_offset(),
2437  1e-6):
2438  raise ValueError("UnitsSet equality check failed")
2439  return representative
2440 
2441  def set_expression(self, expr):
2442  """Store a reference to the expression that has these units."""
2443  self._expression = expr
2444  return
2445 
2446  def _add_source(self, units, src_units_set, src_units):
2447  """Add source units for the given units.
2448 
2449  This stores references to how units were arrived at when doing a
2450  simplify. It manages lists of (src_units_set, src_units) pairs for
2451  each units definition in this set.
2452 
2453  If src_units_set has no associated expression, then it is
2454  considered to be a temporary object, created for example whilst
2455  doing an n-ary times operation. In this case, we add its list of
2456  sources for src_units to our sources list instead.
2457  """
2458  if not units in self._sources:
2459  self._sources[units] = []
2460  if not hasattr(src_units_set, '_expression'):
2461  print self.description(), units.description()
2462  print src_units_set.description(), src_units.description()
2463  if src_units_set._expression:
2464  self._sources[units].append((src_units_set, src_units))
2465  else:
2466  try:
2467  self._sources[units].extend(src_units_set._sources[src_units])
2468  except KeyError:
2469  # No sources list found. This can happen if we do
2470  # dimensionless.simplify(...), for example, to evaluate powers.
2471  pass
2472  return
2473 
2474  def _get_sources(self, units):
2475  """Return the sources list for the given units."""
2476  return self._sources.get(units, [])
2477 
2478  def get_expression(self):
2479  """Return an expression that has these units."""
2480  return self._expression
2481 
2482  def simplify(self, other_units=None, other_exponent=1):
2483  """Simplify the units in this set.
2484 
2485  Each cellml_units object in this set is simplified, and a new
2486  UnitsSet returned with the results.
2487 
2488  If other_units is not None, then products of units are
2489  calculated. The returned set is essentially the cartesian
2490  product of the input sets under the simplify operator, i.e.
2491  u1.simplify(other_units=u2, other_exponent=other_exponent)
2492  will be called for each member u1 of self and each member u2
2493  of other_units (if other_units is a UnitsSet; otherwise
2494  u2=other_units).
2495  """
2496  result_set = UnitsSet()
2497  for units in self:
2498  if other_units is None:
2499  res_u = units.simplify()
2500  result_set.add(res_u)
2501  result_set._add_source(res_u, self, units)
2502  else:
2503  if isinstance(other_units, cellml_units):
2504  other_units = UnitsSet([other_units])
2505  for u in other_units:
2506  res_u = units.simplify(u, other_exponent)
2507  result_set.add(res_u)
2508  result_set._add_source(res_u, self, units)
2509  result_set._add_source(res_u, other_units, u)
2510  return result_set
2511 
2512  def dimensionally_equivalent(self, other_units):
2513  """Check for dimensional equivalence between sets of units.
2514 
2515  Since all units in each set should be equivalent, we just compare
2516  an arbitrary member from each set.
2517 
2518  other_units may be a single cellml_units instance, in which case we
2519  compare an arbitrary member of self to it.
2520  """
2521  u1 = self.extract()
2522  u2 = other_units.extract()
2523  return u1.dimensionally_equivalent(u2)
2524 
2525  def description(self):
2526  """Describe these units.
2527 
2528  Shows the descriptions of each member, as though this were a set of
2529  unicode strings. If multiple members have the same description,
2530  only one instance is shown. If only one description is being shown,
2531  then the curly brackets are not added.
2532  """
2533  desc = list(set(u.description() for u in self))
2534  desc.sort()
2535  if len(desc) > 1:
2536  desc = u'{' + u','.join(desc) + u'}'
2537  else:
2538  desc = desc[0]
2539  return desc
2540 
2542  """
2543  Specialised units class.
2544  Contains useful methods for defining the standard units dictionary,
2545  and checking for units consistency.
2546 
2547  After being defined, a units definition should be regarded as being
2548  immutable, as should individual <unit> elements, so the expansion
2549  and simplification methods create new objects, which don't really
2550  live in the document tree (they're not a child of any element in the
2551  model), although they do have their xml_parent attribute set.
2552 
2553  Note that <unit> elements must not be made a child of more than one
2554  <units> element, otherwise the linked lists get tangled up.
2555  """
2556 
2557  def __init__(self):
2558  super(cellml_units, self).__init__()
2559  self._cml_expanded = None
2561  self._cml_generated = False
2562  self._cml_quotients = {}
2563  self._cml_hash = None
2564  return
2565 
2566  def __repr__(self):
2567  return '<cellml_units %s @ 0x%x>' % (self.name, id(self))
2568 
2569  @property
2570  def _hash_tuple(self):
2571  """Generate a tuple used as the basis for our hash value."""
2572  if self._cml_hash is None:
2573  hash_list = [self.is_base_unit()]
2574  if not self._cml_generated:
2575  hash_list.append(self.name)
2576  hash_list.extend(list(getattr(self, u'unit', [])))
2577  hash_tup = tuple(hash_list)
2578  self._cml_hash_tup = hash_tup
2579  self._cml_hash = hash(hash_tup)
2580  return self._cml_hash_tup
2581 
2582  @property
2583  def uniquify_tuple(self):
2584  """For avoiding duplicate identical units definitions.
2585 
2586  Based on description(cellml=True), since that is what we really want to be unique.
2587  Also includes offset information, since that is omitted in the name given by description.
2588  """
2589  l = [self.description(cellml=True)]
2590  if self.is_simple():
2591  # Include offset information
2592  l.append((self.unit.get_units_element().uniquify_tuple,
2593  self.unit.get_offset()))
2594  return tuple(l)
2595 
2596  def __hash__(self):
2597  """Generate a hash for these units.
2598 
2599  Hashes a tuple, the first element of which is the result of self.is_base_unit(),
2600  the second of which is our name if we are not auto-generated,
2601  and the remaining elements of which are our <unit> elements.
2602  """
2603  if self._cml_hash is None:
2604  _ = self._hash_tuple
2605  return self._cml_hash
2606 
2607  def __cmp__(self, other):
2608  """Compare 2 units objects, by comparing their hashes.
2609 
2610  Means using units as dictionary keys is more sane."""
2611  if isinstance(other, cellml_units):
2612  if hash(self) == hash(other):
2613  return cmp(self._cml_hash_tup, other._cml_hash_tup)
2614  else:
2615  return cmp(hash(self), hash(other))
2616  else:
2617  return super(cellml_units, self).__cmp__(other)
2618 
2619 # The following currently causes infinite loops/recursions
2620 ## def __eq__(self, other):
2621 ## """Compare 2 units elements for equality.
2622 ## return self.equals(other)
2623 ## def __ne__(self, other):
2624 ## """Inverse of self.__eq__(other)."""
2625 ## return not self.__eq__(other)
2626 
2627  def _rel_error_ok(self, value1, value2, tol):
2628  """Test if the relative difference of 2 values is within tol."""
2629  if abs(value1) == 0.0:
2630  return abs(value2) < tol
2631  else:
2632  return (abs(value1-value2)/abs(value1)) < tol
2633 
2634  def equals(self, other):
2635  """Compare 2 units elements for equality.
2636 
2637  Two units are deemed equal if they are both dimensionally equivalent and have the same
2638  multiplicative factor (to within a relative tolerance of 10^-6).
2639 
2640  If both are simple units, they must also have the same offset.
2641  """
2642  equal = isinstance(other, cellml_units) and \
2643  self.dimensionally_equivalent(other) and \
2644  self._rel_error_ok(self.expand().get_multiplicative_factor(),
2645  other.expand().get_multiplicative_factor(),
2646  1e-6)
2647  if equal and self.is_simple():
2648  equal = self._rel_error_ok(self.unit.get_offset(),
2649  other.unit.get_offset(),
2650  1e-6)
2651  return equal
2652 
2653  @property
2654  def model(self):
2655  return self.rootNode.model
2656 
2657  def extract(self, check_equality=False):
2658  """Return these units.
2659 
2660  Used for interface compatibility with UnitsSet."""
2661  return self
2662 
2663  def copy(self):
2664  """Return a new UnitsSet containing this cellml_units object.
2665 
2666  Used for interface compatibility with UnitsSet, where the method performs a shallow copy.
2667  """
2668  return UnitsSet([self])
2669 
2670  def description(self, force=False, cellml=False):
2671  """Return a human-readable name for these units.
2672 
2673  The name will be descriptive and based on the consituent <unit> elements, e.g. 'volt per second^2'
2674 
2675  By default, if these are user-defined units, then return self.name. Set force to True to override this behaviour.
2676 
2677  Set cellml to True to get a description that is also a valid CellML identifier.
2678  """
2679  if self.is_base_unit():
2680  desc = self.name
2681  elif not force and not self._cml_generated:
2682  desc = self.name
2683  else:
2684  descs, per_descs = [], []
2685  # Multiplier
2686  m = self.get_multiplier()
2687  if m != 1:
2688  descs.append(unicode(m))
2689  # Constituent units
2690  dimensionless = self.get_units_by_name('dimensionless')
2691  for unit in self.unit:
2692  if unit.get_units_element() is dimensionless:
2693  continue
2694  desc = [getattr(unit, u'prefix_', u''), unit.get_units_element().name]
2695  e = unit.get_exponent()
2696  if int(e) == e:
2697  # Cast to integer so name looks nicer.
2698  e = int(e)
2699  if abs(e) != 1:
2700  desc.extend(['^', str(abs(e))])
2701  desc = u''.join(desc)
2702  if e < 0:
2703  per_descs.append(desc)
2704  else:
2705  descs.append(desc)
2706  # Sort unit descriptions for readability
2707  descs.sort()
2708  descs = u' '.join(descs)
2709  per_descs.sort()
2710  desc = u' per '.join([descs] + per_descs)
2711  if not desc:
2712  desc = u'dimensionless'
2713  elif descs:
2714  desc = u"'" + desc + u"'"
2715  else:
2716  # Remove unwanted space from the start
2717  desc = u"'" + desc[1:] + u"'"
2718  # Convert to CellML identifier?
2719  if cellml:
2720  desc = desc.replace(u"'", u"").replace(u"^", u"")
2721  desc = desc.replace(u"*", u"").replace(u".", u"_")
2722  desc = desc.replace(u" ", u"_")
2723  return desc
2724 
2725  def get_units_by_name(self, uname):
2726  """Return an object representing the element that defines the units named `uname'."""
2727  # Look up units in our parent model or component element instead
2728  return self.xml_parent.get_units_by_name(uname)
2729 
2730  def expand(self):
2731  """Expand to a product of powers of base units.
2732 
2733  Expand this units definition according to the algorithm given in appendix C.3.4 of the CellML spec.
2734 
2735  Caches and returns the resulting <units> object, which will be
2736  newly created if there are any changes made, or this object if not.
2737  """
2738  if self._cml_expanded is None:
2739  # Do the expansion
2740  if self.is_base_unit():
2741  # We are a base unit, so stop the recursion; the result is us
2742  self._cml_expanded = self
2743  elif self.is_simple():
2744  # Simple units definition, i.e. only one <unit> element,
2745  # exponent=1, and referenced units are simple or base.
2746  # Expand the units referenced by our <unit> element
2747  expanded_child = self.unit.get_units_element().expand()
2748  # Get our multiplicative factor & offset as numbers
2749  m1p1 = self.unit.get_multiplicative_factor()
2750  o1 = self.unit.get_offset()
2751  if expanded_child.is_base_unit():
2752  # New multiplier, etc. are just ours
2753  m_new = m1p1
2754  o_new = o1
2755  # Referenced units are expanded_child
2756  ref_obj_new = expanded_child
2757  else:
2758  # Get the multiplier & offset of our expanded child
2759  m2p2 = expanded_child.unit.get_multiplicative_factor()
2760  o2 = expanded_child.unit.get_offset()
2761  # Calculate new multiplier, etc. per Equation (11)
2762  m_new = m1p1*m2p2
2763  o_new = o1 + o2/m1p1
2764  # Referenced units are those referenced by expanded_child
2765  # These will be base units
2766  ref_obj_new = expanded_child.unit.get_units_element()
2767  # Create the new units & unit elements
2768  attrs = {u'name': self.name}
2769  self._cml_expanded = self.xml_create_element(u'units',
2770  NSS[u'cml'],
2771  attributes=attrs)
2772  self._cml_expanded._cml_generated = True
2773  attrs = {u'units': ref_obj_new.name,
2774  u'multiplier': unicode(m_new),
2775  u'offset': unicode(o_new)}
2776  unit = self.xml_create_element(u'unit', NSS[u'cml'],
2777  attributes=attrs)
2778  # Manually set the reference object for unit, since we have
2779  # it handy
2780  unit._set_units_element(ref_obj_new)
2781  self._cml_expanded.xml_append(unit)
2782  else:
2783  # Complex units definition, i.e. multiple <unit> elements,
2784  # or non-unitary exponent, at some point within this defn.
2785  # Create the new units element so we can add children to it
2786  attrs = {u'name': self.name}
2787  exp_units = self.xml_create_element(u'units', NSS[u'cml'],
2788  attributes=attrs)
2789  exp_units._cml_generated = True
2790  # Compute m_t (Equation (18)) expanding referenced
2791  # units as we go
2792  m_t = 1
2793  for unit in self.unit:
2794  m_t *= unit.get_multiplicative_factor() # * is assoc. :)
2795  # We'll need the exponent to modify units referenced
2796  # by this reference
2797  e = unit.get_exponent()
2798  # Expand referenced units
2799  exp_u = unit.get_units_element().expand()
2800  if exp_u.is_base_unit():
2801  # Create reference to exp_u
2802  attrs = {u'units': exp_u.name,
2803  u'exponent': unicode(e)}
2804  u = self.xml_create_element(u'unit', NSS[u'cml'],
2805  attributes=attrs)
2806  exp_units.xml_append(u)
2807  else:
2808  # Process units referenced by exp_u, which will be
2809  # base units.
2810  for u in exp_u.unit:
2811  m_t *= u.get_multiplicative_factor() ** e
2812  attrs = {u'units': u.units,
2813  u'exponent': unicode(
2814  u.get_exponent() * e)}
2815  u_new = u.xml_create_element(u'unit',
2816  NSS[u'cml'],
2817  attributes=attrs)
2818  exp_units.xml_append(u_new)
2819  # Set the multiplier for the expanded units to m_t.
2820  # Since a <units> elements doesn't have a multiplier
2821  # attribute, we set it on the first <unit> element.
2822  # Note that all the <unit> elements have been newly created,
2823  # so have an implicit multiplier of 1 currently.
2824  # Note also that the fact that each <unit> has an exponent
2825  # doesn't matter, since the exponent doesn't affect the
2826  # multiplier.
2827  # Alan pointed out a bug: if the <unit> elements are
2828  # in non-canonical order then we can get identical
2829  # units (according to the intended canonical form)
2830  # comparing as non-equal, because expansion put the
2831  # multiplicative factor on different <unit> elements
2832  # in each case. Hence we have to put the factor on
2833  # the first <unit> element according to a canonical
2834  # sorting order.
2835  # TODO: Be a bit cleverer about this? In the base unit case
2836  # above we could do exp_units.xml_append(unit.clone()) and
2837  # not have its multiplicative factor contributing to m_t.
2838  # Would then need to multiply here, since first unit may
2839  # already have multiplier != 1.
2840  # Alternative idea from Alan: I have just noticed that
2841  # when expanding a complex units definition based on a
2842  # complex units definition, we can get away with not
2843  # sorting the unit elements. All that is required is
2844  # to create a new unit element which type is
2845  # dimensionless and value of its multiplier attribute
2846  # m*10^(p*e). This avoids having to sort things and
2847  # propagating the multiplier attribute...
2848  first_unit = sorted(exp_units.unit, key=lambda u: u.units)[0]
2849  first_unit.multiplier = unicode(m_t)
2850  # Cache result
2851  self._cml_expanded = exp_units
2852  # Set parent of the expanded units to be our parent
2853  self._cml_expanded.xml_parent = self.xml_parent
2854  # Returned the cached result
2855  return self._cml_expanded
2856 
2857  def is_base_unit(self):
2858  """Return True iff this is a base units definition."""
2859  return getattr(self, u'base_units', u'no') == u'yes'
2860  def is_simple(self):
2861  """Return True iff this is a simple units definition.
2862 
2863  Units are simple if:
2864  there is 1 <unit> element
2865  the exponent is omitted or has value 1.0
2866  the referenced units are simple or base units
2867  """
2868  simple = False
2869  if not self.is_base_unit() and len(self.unit) == 1:
2870  if self.unit.get_exponent() == 1.0:
2871  u = self.unit.get_units_element()
2872  if u.is_base_unit() or u.is_simple():
2873  simple = True
2874  return simple
2875 
2877  """Return the multiplicative factor of this units definition.
2878 
2879  The multiplicative factor of a units definition can be defined as
2880  the product of the multiplicative factors of its unit children.
2881  """
2882  m = reduce(operator.mul,
2883  map(lambda unit: unit.get_multiplicative_factor(),
2884  getattr(self, u'unit', [])),
2885  1)
2886  return m
2887 
2888  def get_multiplier(self):
2889  """Return the multiplier of this units definition.
2890 
2891  The multiplier of a units definition can be defined as the product
2892  of the multipliers of its unit children.
2893  """
2894  return reduce(operator.mul,
2895  map(lambda unit: unit.get_multiplier(),
2896  getattr(self, u'unit', [])),
2897  1)
2898 
2899  def get_offset(self):
2900  """Return the offset associated with this units definition.
2901 
2902  If these are simple units, return the offset on our unit child.
2903  Otherwise, return 0.
2904  """
2905  if self.is_simple():
2906  o = self.unit.get_offset()
2907  else:
2908  o = 0
2909  return o
2910 
2911  @staticmethod
2912  def create_new(parent, name, bases, add_to_parent=False, standard=False):
2913  """Create a new units definition element.
2914 
2915  It requires either a cellml_model or cellml_component element
2916  to be passed as the parent for the definition. If
2917  add_to_parent is set to true the new units element will be
2918  appended to parent's children.
2919 
2920  The bases parameter should be a list of dictionaries suitable
2921  for use as the keyword arguments of cellml_units._based_on.
2922  If the list is empty it will be defined as a base unit.
2923  """
2924  # Create the units element
2925  attrs = {u'name': unicode(name)}
2926  if not bases:
2927  attrs[u'base_units'] = u'yes'
2928  if standard:
2929  attrs[u'standard'] = u'yes'
2930  u = parent.xml_create_element(u'units', NSS[u'cml'], attributes=attrs)
2931  if add_to_parent:
2932  parent.xml_append(u)
2933  else:
2934  u.xml_parent = parent # Hack so units lookups work
2935  # Add references to units we're based on
2936  for basis in bases:
2937  u._based_on(**basis)
2938  return u
2939 
2940  def _based_on(self, units=None, prefix=None, exponent=None,
2941  multiplier=None, offset=None):
2942  """Convenience function for defining new units."""
2943  for v in ['units', 'prefix', 'exponent', 'multiplier', 'offset']:
2944  # Coerce from str to unicode
2945  exec "if type(%s) == str: %s = unicode(%s)" % (v,v,v)
2946  # Check type
2947  exec "assert(%s is None or type(%s) == unicode)" % (v,v)
2948  assert(not hasattr(self, 'base_units') or self.base_units == u'no')
2949  attrs = {u'units': units}
2950  if offset:
2951  # Simple units definition
2952  assert(not hasattr(self, 'unit'))
2953  attrs[u'offset'] = offset
2954  if not prefix is None: attrs[u'prefix'] = prefix
2955  if not exponent is None: attrs[u'exponent'] = exponent
2956  else:
2957  # Complex units definition
2958  if not prefix is None: attrs[u'prefix'] = prefix
2959  if not exponent is None: attrs[u'exponent'] = exponent
2960  if not multiplier is None: attrs[u'multiplier'] = multiplier
2961  self.xml_append(self.xml_create_element(u'unit', NSS[u'cml'],
2962  attributes=attrs))
2963  return
2964 
2965  # Class attribute, so all <units> elements share the same value.
2966  units_name_counter = [0]
2967  def simplify(self, other_units=None, other_exponent=1):
2968  """Simplify these units.
2969 
2970  Create a new <units> element representing a simplified version of
2971  this element. This implements the algorithm of appendix C.3.1. It
2972  is however slightly different, in order to allow for units
2973  conversions rather than just preserving dimensional equivalence.
2974 
2975  If other_units is not None, then produce a simplified version of
2976  the product of these units and other_units. other_units are
2977  considered to be raised to the power of other_exponent (so a
2978  quotient can be performed by passing other_exponent=-1). Note that
2979  other_exponent should have numeric type.
2980 
2981  If other_units is a UnitsSet, then we construct a UnitsSet
2982  containing self, and call the simplify method on that, thus
2983  returning a UnitsSet instance.
2984 
2985  Multiplicative factors on the original <unit> objects are
2986  maintained on the generated references, by taking the product of
2987  all factors from <unit> objects that contribute to the new <unit>
2988  object. Note that this means that we may need to retain a
2989  reference to dimensionless with a multiplier, for example if the
2990  quotient of centimetres by metres is taken.
2991 
2992  Offsets are only permitted on simple units, so may not appear where
2993  there are multiple <unit> elements. Hence when a product of units
2994  is taken, any offsets are discarded.
2995  """
2996  if isinstance(other_units, UnitsSet):
2997  # Use the set version of simplify
2998  return UnitsSet([self]).simplify(other_units, other_exponent)
2999 
3000  # Check for result in cache (see #1714)
3001  if (other_units, other_exponent) in self._cml_simplified:
3002  return self._cml_simplified[(other_units, other_exponent)]
3003 
3004  # Make a list of all <unit> elements to be included
3005  units = []
3006  if self.is_base_unit():
3007  # We are a base unit, so invent a reference to ourselves
3008  u = self.xml_create_element(u'unit', NSS[u'cml'],
3009  attributes={u'units': self.name})
3010  u.xml_parent = self # Hack, but it might need a parent...
3011  u._set_units_element(self)
3012  units.append(u)
3013  else:
3014  units = list(self.unit)
3015  our_unit_elements = frozenset(units)
3016  other_unit_elements = None
3017  if not other_units is None:
3018  if other_units.is_base_unit():
3019  # other_units are base units, so invent a reference
3020  attrs = {u'units': other_units.name,
3021  u'exponent': unicode(other_exponent)}
3022  u = self.xml_create_element(u'unit', NSS[u'cml'],
3023  attributes=attrs)
3024  u.xml_parent = other_units
3025  u._set_units_element(other_units)
3026  units.append(u)
3027  if other_exponent == 1:
3028  other_unit_elements = frozenset([u])
3029  else:
3030  if other_exponent == 1:
3031  units.extend(list(other_units.unit))
3032  other_unit_elements = frozenset(other_units.unit)
3033  else:
3034  for unit in other_units.unit:
3035  # Need to create a new <unit> element with different
3036  # exponent and multiplier
3037  u = unit.clone()
3038  u.exponent = unicode(other_exponent *
3039  u.get_exponent())
3040  u.multiplier = unicode(u.get_multiplier() **
3041  other_exponent)
3042  units.append(u)
3043  # Sort <unit> elements according to the <units> objects they
3044  # reference
3045  dimensionless = self.get_units_by_name(u'dimensionless')
3046  d = {dimensionless: []}
3047  for unit in units:
3048  obj = unit.get_units_element()
3049  if obj in d:
3050  d[obj].append(unit)
3051  else:
3052  d[obj] = [unit]
3053  # Collapse equivalent units references into a single reference.
3054  # That is, all references to the same object get collapsed into a new
3055  # reference to that object with different exponent, etc.
3056  for obj in d.keys():
3057  if obj != dimensionless and len(d[obj]) > 1:
3058  # Sum the exponents
3059  expt = sum(map(lambda u: u.get_exponent(), d[obj]))
3060  # If exponents cancel, replace with ref to dimensionless
3061  if expt == 0:
3062  attrs = {u'units': u'dimensionless'}
3063  else:
3064  attrs = {u'units': d[obj][0].units,
3065  u'exponent': unicode(expt)}
3066  # Compute the multiplier for the new unit reference, as
3067  # the product of multiplicative factors on the originals
3068  m = reduce(operator.mul,
3069  map(lambda u: u.get_multiplicative_factor(),
3070  d[obj]))
3071  attrs[u'multiplier'] = unicode(m)
3072  # Create a new reference
3073  new = self.xml_create_element(u'unit', NSS[u'cml'],
3074  attributes=attrs)
3075  new.xml_parent = self
3076  if expt == 0:
3077  # Note an extra reference to dimensionless...
3078  d[dimensionless].append(new)
3079  # ...and remove the references to obj from d
3080  del d[obj]
3081  else:
3082  d[obj] = new
3083  elif obj != dimensionless and d[obj]:
3084  # d[obj] is a singleton list. Create a new reference and
3085  # store it instead (a new reference is needed to avoid
3086  # altering the linked list from self.unit).
3087  d[obj] = d[obj][0].clone()
3088  # Note d must have at least one key, namely dimensionless
3089  # If dimensionless is referenced in addition to other units,
3090  # remove the references to dimensionless.
3091  # But remember the multipliers!
3092  m = reduce(operator.mul,
3093  map(lambda u: u.get_multiplicative_factor(),
3094  d[dimensionless]),
3095  1)
3096  if m == 1:
3097  del d[dimensionless]
3098  else:
3099  # Retain a single reference to dimensionless, with the
3100  # combined multiplier.
3101  d[dimensionless] = d[dimensionless][0].clone()
3102  d[dimensionless].multiplier = unicode(m)
3103 ## print "Keeping d'less ref with m =",m,"from",
3104 ## print self.description(),
3105 ## if other_units:
3106 ## print "and",other_units.description()
3107 ## else:
3108 ## print
3109  if not d:
3110  # The units definition only referenced dimensionless, and
3111  # the multiplier was 1, so we can replace it by dimensionless
3112  # itself
3113  new_units = dimensionless
3114  else:
3115  # Avoid creating new <units> elements unnecessarily
3116  unit_elements = set(d.values())
3117  if unit_elements == our_unit_elements:
3118  new_units = self
3119  elif unit_elements == other_unit_elements:
3120  new_units = other_units
3121  else:
3122  # Create new <units> element
3123  self.units_name_counter[0] += 1
3124  uname = u'___units_' + str(self.units_name_counter[0])
3125 ## print ".simplify", uname
3126  new_units = self.xml_create_element(
3127  u'units', NSS[u'cml'], attributes={u'name': uname})
3128  new_units._cml_generated = True
3129  # Set its parent to be ours or other_units'
3130  new_units.xml_parent = self._best_parent(other_units)
3131  # Add <unit> children
3132  for unit in unit_elements:
3133  new_units.xml_append(unit)
3134  if self.model._is_new_units_obj(new_units):
3135  # Add name to units dictionary
3136 ## print "Adding",uname,hash(new_units),new_units.description()
3137  new_units.xml_parent.add_units(uname, new_units)
3138  # Cache and return result
3139  new_units = self.model._get_units_obj(new_units)
3140  self._cml_simplified[(other_units, other_exponent)] = new_units
3141  return new_units
3142 
3143  def _best_parent(self, other_units):
3144  """Return a suitable parent for units formed from self and other_units.
3145 
3146  If either constituent is in a component, that component should be the
3147  parent, otherwise the model should be.
3148  """
3149  p1, p2 = self.xml_parent, other_units and other_units.xml_parent
3150  if p2 and p1 != p2 and isinstance(p1, cellml_model):
3151  p1 = p2
3152  return p1
3153 
3154  def quotient(self, other_units):
3155  """Form the quotient of two units definitions.
3156 
3157  This method does not simplify the resulting units.
3158  Quotient units will be cached.
3159  """
3160  if not other_units in self._cml_quotients:
3161  # Create new <units> element
3162  self.units_name_counter[0] += 1
3163  uname = u'___units_' + str(self.units_name_counter[0])
3164  quotient_units = self.xml_create_element(
3165  u'units', NSS[u'cml'], attributes={u'name': uname})
3166  quotient_units._cml_generated = True
3167  quotient_units.xml_parent = self._best_parent(other_units)
3168  # Invent references to the constituent units
3169  u = self.xml_create_element(u'unit', NSS[u'cml'],
3170  attributes={u'units': self.name})
3171  u._set_units_element(self)
3172  quotient_units.xml_append(u)
3173  u = self.xml_create_element(u'unit', NSS[u'cml'],
3174  attributes={u'units': self.name,
3175  u'exponent': u'-1'})
3176  u._set_units_element(other_units)
3177  quotient_units.xml_append(u)
3178  # Cache
3179  if self.model._is_new_units_obj(quotient_units):
3180  quotient_units.xml_parent.add_units(uname, quotient_units)
3181  quotient_units = self.model._get_units_obj(quotient_units)
3182  self._cml_quotients[other_units] = quotient_units
3183  return self._cml_quotients[other_units]
3184 
3185  def dimensionally_equivalent(self, other_units):
3186  """Return True iff other_units is dimensionally equivalent to self.
3187 
3188  As per appendix C.2.2, two units definitions have dimensional
3189  equivalence if, when each is recursively expanded and
3190  simplified into a product of base units, each has the same set
3191  of base units with the same exponent on corresponding base units.
3192  """
3193  u1 = self.expand().simplify()
3194  if isinstance(other_units, UnitsSet):
3195  other_units = other_units.extract()
3196  u2 = other_units.expand().simplify()
3197  # Build dictionaries mapping base_unit_obj to exponent.
3198  d1, d2 = {}, {}
3199  if u1.is_base_unit():
3200  d1[u1] = 1
3201  else:
3202  for u in u1.unit:
3203  d1[u.get_units_element()] = u.get_exponent()
3204  if u2.is_base_unit():
3205  d2[u2] = 1
3206  else:
3207  for u in u2.unit:
3208  d2[u.get_units_element()] = u.get_exponent()
3209  # Compare keys: do u1 & u2 have the same set of base units?
3210  sym_diff = set(d1.keys()) ^ set(d2.keys())
3211  if sym_diff:
3212  dimensionless = self.get_units_by_name(u'dimensionless')
3213  if not sym_diff == set([dimensionless]):
3214  # Symmetric difference is non-empty, but not an ignorable
3215  # instance of dimensionless
3216  return False
3217  # Compare corresponding exponents
3218  for k in d1:
3219  try:
3220  if d1[k] != d2[k]: return False
3221  except KeyError:
3222  # One may have dimensionless as a key
3223  pass
3224  # We have a match!
3225  return True
3226 
3227 
3229  """Specialised class for <unit> elements.
3230 
3231  Maintains a reference to the object representing the units definition
3232  it references, provides some helpful accessor type methods, and allows
3233  safe, easy cloning of <unit> elements.
3234  """
3235 
3236  def __init__(self):
3237  element_base.__init__(self)
3238  self._cml_units_obj = None
3239  return
3240 
3241  def _hash_tup(self):
3242  """Create a tuple to be used for hashing/equality tests."""
3243  return (self.get_units_element(), getattr(self, u'prefix_', ''),
3244  self.get_multiplier(), self.get_exponent(),
3245  self.get_offset())
3246 
3247  def __eq__(self, other):
3248  """Compare two <unit> elements.
3249 
3250  Two <unit> elements are equal if they reference the same <units>
3251  element, and have the same prefix, multiplier, etc.
3252  """
3253  eq = False
3254  if isinstance(other, cellml_unit):
3255  eq = self._hash_tup() == other._hash_tup()
3256  return eq
3257  def __ne__(self, other):
3258  """The inverse of self.__eq__(other)."""
3259  return not self.__eq__(other)
3260  def __hash__(self):
3261  """Richer hashing function than the default based on object id.
3262 
3263  Returns the hash of a tuple of relevant attributes."""
3264  return hash(self._hash_tup())
3265 
3267  """
3268  Return the object representing the <units> element that this <unit> element references.
3269  """
3270  if self._cml_units_obj is None:
3271  # Chase the reference and cache it
3272  self._cml_units_obj = self.xml_parent.get_units_by_name(self.units)
3273  return self._cml_units_obj
3274  def _set_units_element(self, obj, override=False):
3275  """
3276  Set the object representing the <units> element that this <unit> element references.
3277 
3278  Don't use unless you know what you're doing.
3279  """
3280  assert override or self._cml_units_obj is None
3281  self._cml_units_obj = obj
3282  return
3283 
3284  SI_PREFIXES = {"yotta": 24, "zetta": 21, "exa": 18, "peta": 15,
3285  "tera": 12, "giga": 9, "mega": 6, "kilo": 3,
3286  "hecto": 2, "deka": 1,
3287  "deci": -1, "centi": -2,
3288  "milli": -3, "micro": -6, "nano": -9, "pico": -12,
3289  "femto": -15, "atto": -18, "zepto": -21, "yocto": -24}
3290 
3292  """Return the factor this units reference is multiplied by.
3293 
3294  Return the quantity m.p^e as a floating point number, where:
3295  m is the multiplier (default value 1.0)
3296  p is the multiplicative factor due to the prefix (default 10^0=1)
3297  e is the exponent (default 1)
3298  """
3299  m = self.get_multiplier()
3300  e = self.get_exponent()
3301  p = getattr(self, u'prefix_', 0) # Since prefix is a method :(
3302  if p in self.SI_PREFIXES:
3303  p = self.SI_PREFIXES[p]
3304  else:
3305  p = int(p) # RELAX NG schema says it's an integer
3306  p = 10**p
3307  return m * (p**e)
3308 
3309  def get_multiplier(self):
3310  """Return the multiplier of this units reference, as a float."""
3311  return float(getattr(self, u'multiplier', 1))
3312 
3313  def get_exponent(self):
3314  """Return the exponent of this units reference, as a float."""
3315  return float(getattr(self, u'exponent', 1))
3316 
3317  def get_offset(self):
3318  """Return the offset of this units reference, as a float."""
3319  return float(getattr(self, u'offset', 0))
3320 
3321  def clone(self):
3322  """Clone this object.
3323 
3324  Return a new <unit> element that has the same attributes as this
3325  one.
3326  """
3327  attrs = {}
3328  for apyname, aname in self.xml_attributes.iteritems():
3329  attrs[aname] = getattr(self, apyname)
3330  new = self.xml_create_element(u'unit', NSS[u'cml'], attributes=attrs)
3331  if self._cml_units_obj:
3332  new._set_units_element(self._cml_units_obj)
3333  new.xml_parent = self.xml_parent
3334  return new
3335 
3336 
3337 class EvaluationError(Exception):
3338  """
3339  Exception class for errors raised trying to evaluate a MathML expression.
3340  """
3341  pass
3342 
3343 class MathsError(Exception):
3344  """
3345  Exception class for validation errors raised while checking mathematics.
3346  """
3347  def __init__(self, context_obj, message, warn=False, level=None):
3348  """Create a mathematics validation error.
3349 
3350  context_class should be the object that is reporting the error.
3351  message gives more details on what went wrong.
3352  If warn is set to true then produce a warning message, not an error.
3353  level gives the level of the message logged.
3354  """
3355  self.message = message
3356  self.warn = warn
3357  self.level = level or (logging.ERROR,logging.WARNING)[warn]
3358  self.show_xml_context = False
3360 
3361  self.cname = context_obj.component.name
3362  self.ename = context_obj.localName
3363  self.context = context_obj
3364 
3365  # Nicer context explanation
3366  if isinstance(context_obj, cellml_variable):
3367  self.expr_index = self.math_index = 0
3368  self.reaction_spec = ''
3369  return
3370  expr_root = context_obj.xml_xpath(u'ancestor-or-self::*[local-name(..)="math"]')[0]
3371  self.expr_index = self.math_index = 1
3372  math = expr_root.xml_parent
3373  for elt in math.xml_element_children():
3374  if elt is expr_root: break
3375  if elt.localName in [u'apply', u'semantics']:
3376  self.expr_index += 1
3377  for elt in math.xml_element_children(math.xml_parent):
3378  if elt is math: break
3379  if elt.localName == u'math':
3380  self.math_index += 1
3381  # Is this in a reaction?
3382  vref = context_obj.xml_xpath(u'ancestor::cml:variable_ref')
3383  if vref:
3384  self.reaction_spec = ' in mathematics for variable "%s" in a reaction' % vref[0].variable
3385  else:
3386  self.reaction_spec = ''
3387  return
3388 
3390  """Only show the XML where the error occurred."""
3391  self.show_xml_context = True
3392  self._show_xml_context_only = True
3393 
3394  def __str__(self):
3395  msg = unicode(self)
3396  return msg.encode('UTF-8')
3397 
3398  def ordinal(self, i):
3399  "Convert integer i to an ordinal string."
3400  if i // 10 == 1: suf = 'th'
3401  elif i % 10 == 1: suf = 'st'
3402  elif i % 10 == 2: suf = 'nd'
3403  elif i % 10 == 3: suf = 'rd'
3404  else: suf = 'th'
3405  return "%d%s" % (i, suf)
3406 
3407  def _generate_message(self, where):
3408  if self.warn: type = 'Warning'
3409  else: type = 'Error'
3410  msg = [type, ' ', where, ': ', self.message]
3411  if not self._show_xml_context_only:
3412  msg.extend(['\n Context: ', self.ordinal(self.expr_index),
3413  ' expression in the ', self.ordinal(self.math_index),
3414  ' math element', self.reaction_spec,
3415  ' in component ', self.cname, '\n XPath: ',
3416  element_xpath(self.context)])
3417  msg = u''.join(msg)
3418  if self.show_xml_context:
3419  # Return the context XML tree as well.
3420  xml = self.context.xml(indent = u'yes',
3421  omitXmlDeclaration = u'yes')
3422  msg = msg + u'\n' + unicode(xml, encoding='UTF-8')
3423  return msg
3424 
3425  def __unicode__(self):
3426  return self._generate_message('checking mathematics')
3427 
3428 
3430  """
3431  Exception class for validation errors raised during units checking
3432  of mathematics.
3433  """
3434  def __init__(self, context_obj, message, warn=False, level=None):
3435  """Create a units validation error.
3436 
3437  context_class should be the object that is reporting the error.
3438  message gives more details on what went wrong.
3439  If warn is set to true then produce a warning message, not an error.
3440  level gives the level of the message logged.
3441  """
3442  MathsError.__init__(self, context_obj, message, warn=warn, level=level)
3443  return
3444 
3445  def __unicode__(self):
3446  return self._generate_message('checking units')
3447 
3448 
3449 def child_i(elt, i):
3450  "Return the i'th child element of elt. Indexing starts at 1."
3451  j = 0
3452  for e in elt.xml_children:
3453  if getattr(e, 'nodeType', None) == Node.ELEMENT_NODE:
3454  j += 1
3455  if j == i: return e
3456  else:
3457  # Not found :(
3458  raise ValueError("<"+elt.localName+"> does not have "+str(i)+
3459  " child element(s).")
3460 def _child1(elt):
3461  "Get the first child element of elt."
3462  return child_i(elt, 1)
3463 
3464 
3465 ######################################################################
3466 # MathML elements #
3467 ######################################################################
3468 
3469 class mathml_units_mixin(object):
3470  """Base class for units mixin classes."""
3471  def _add_units_conversion(self, expr, defn_units, to_units, no_act=False):
3472  """Add mathematics for an explicit units conversion.
3473 
3474  Wraps expr in the expression
3475  m[to_units/defn_units]*(expr-o1[defn_units]) + o2[to_units].
3476  """
3477 # print '_add_units_conv for', element_xpath(expr), 'from', defn_units.description(), 'to', to_units.description()
3478  if hasattr(expr.model, '_cml_special_units_converter') and not defn_units.dimensionally_equivalent(to_units):
3479  # This may be a special conversion case defined by a functional curation protocol
3480  if no_act:
3481  model._cml_conversions_needed = True
3482  return
3483  else:
3484  expr = expr.model._cml_special_units_converter(expr, defn_units, to_units)
3485 # print 'post special, expr units=', expr.get_units().description(), 'm=', expr.get_units().extract().expand().simplify().get_multiplicative_factor()
3486 # print 'orig defn_units=', defn_units.description(), 'm=', defn_units.expand().simplify().get_multiplicative_factor()
3487 # print 'orig to_units=', to_units.description(), 'm=', to_units.expand().simplify().get_multiplicative_factor()
3488  try:
3489  defn_units = expr.get_units().extract(check_equality=True)
3490  except:
3491  print 'ouch', expr.xml()
3492  for u in expr.get_units():
3493  print u.description(), u.get_multiplier(), expr.get_units()._get_sources(u)
3494  raise
3495  defn_units_exp = defn_units.expand().simplify()
3496  to_units_exp = to_units.expand().simplify()
3497  # Conversion factor
3498  m = (defn_units_exp.get_multiplicative_factor() / to_units_exp.get_multiplicative_factor())
3499  # Replace expr by m[to_units/defn_units]*(expr-o1[defn_units]) + o2[to_units]
3500  orig_expr, parent = expr, expr.xml_parent
3501  dummy = expr.xml_create_element(u'dummy', NSS[u'm'])
3502  model = expr.model # So we still have a reference after the next line
3503  parent.replace_child(expr, dummy) # Mark where to put the new elt
3504  if defn_units_exp.get_offset() != 0:
3505  # Create expr-o1 expression
3506  uattr = orig_expr._ensure_units_exist(defn_units, no_act=no_act)
3507  new_expr = mathml_apply.create_new(expr, u'minus',
3508  [expr, (unicode(defn_units_exp.get_offset()), uattr)])
3509  new_expr._cml_units = defn_units
3510  expr = new_expr
3511  if m != 1:
3512  quotient_units = to_units.quotient(defn_units)
3513  # Add units element to model if needed
3514  uattr = orig_expr._ensure_units_exist(quotient_units, no_act=no_act)
3515  # Create m*expr expression
3516  new_expr = mathml_apply.create_new(expr, u'times', [(unicode(m), uattr), expr])
3517  new_expr._cml_units = to_units
3518  expr = new_expr
3519  if to_units_exp.get_offset() != 0:
3520  # Create expr+o2 expression
3521  uattr = orig_expr._ensure_units_exist(to_units, no_act=no_act)
3522  new_expr = mathml_apply.create_new(expr, u'plus',
3523  [expr, (unicode(to_units_exp.get_offset()), uattr)])
3524  new_expr._cml_units = to_units
3525  expr = new_expr
3526  # Note that the model needed conversions
3527  if expr is not orig_expr:
3528  model._cml_conversions_needed = True
3529  if no_act:
3530  expr = orig_expr
3531  parent.replace_child(dummy, expr)
3532  return
3533 
3534  def _set_element_in_units(self, elt, units, no_act=False):
3535  """Try to set the units of the given element.
3536 
3537  Generates a debug message if this isn't possible.
3538  """
3539  if hasattr(elt, '_set_in_units') and callable(elt._set_in_units):
3540  elt._set_in_units(units, no_act)
3541  elif elt.localName in [u'false', u'true']:
3542  boolean = self.component.get_units_by_name('cellml:boolean')
3543  if boolean is not units:
3544  # TODO: *blink* this should never happen
3545  self._add_units_conversion(elt, boolean, units, no_act)
3546  elif elt.localName in [u'notanumber', u'pi', u'infinity', u'exponentiale']:
3547  dimensionless = self.component.get_units_by_name('dimensionless')
3548  if dimensionless is not units:
3549  # TODO: *blink* this should never happen
3550  self._add_units_conversion(elt, dimensionless, units, no_act)
3551  else:
3552  DEBUG('validator',
3553  'Cannot set units (to', units.description(), ') for element', elt.localName)
3554  return
3555 
3557  """Contains the _set_in_units method for ci, cn, etc."""
3558  def _set_in_units(self, units, no_act=False):
3559  """Set the units this element should be expressed in.
3560 
3561  Where these aren't the units it's defined in, replace self by suitable units conversion mathematics.
3562  """
3563  defn_units = self.get_units(return_set=False)
3564  if defn_units != units:
3565  self._add_units_conversion(self, defn_units, units, no_act)
3566  # Store the units
3567  if not no_act:
3568  self._cml_units = units
3569  return
3570 
3572  def _set_in_units(self, units, no_act=False):
3573  """Set the units of the application of this operator.
3574 
3575  The default behaviour for many operators is to simply set all operands to have the given units.
3576  """
3577  # TODO: Do the conversion at this level sometimes rather than pushing it down the tree?
3578  app = self.xml_parent
3579  # We need to convert the operands to a list, because the tree
3580  # may be modified if a conversion is thought to be needed
3581  for operand in list(app.operands()):
3582  self._set_element_in_units(operand, units, no_act)
3583  # And set our units to what they now are
3584  if not no_act:
3585  app._cml_units = units
3586  return
3587 
3589  def _set_in_units(self, units, no_act=False):
3590  """Set the units of the application of this operator.
3591 
3592  This method is used for the relational operators. It ignores the
3593  given units, and instead ensures that all operands have the same
3594  units. The units it chooses are those that are 'least' amongst the
3595  possibilities for the operand units, i.e. that have the smallest
3596  multiplicative factor when expanded.
3597  """
3598  app = self.xml_parent
3599  min_factor, operand_units = None, None
3600  for us in app._get_operand_units():
3601  if isinstance(us, cellml_units):
3602  us = [us]
3603  for u in us:
3604  f = u.expand().get_multiplicative_factor()
3605  if f < min_factor or min_factor is None:
3606  min_factor = f
3607  operand_units = u
3608  # We need to convert the operands to a list, because the tree
3609  # may be modified if a conversion is thought to be needed
3610  for operand in list(app.operands()):
3611  # TODO: Modify tree to collect same conversions together?
3612  self._set_element_in_units(operand, operand_units, no_act)
3613  # And set our units to what they now are
3614  if not no_act:
3615  app._cml_units = units
3616  return
3617 
3619  def _set_in_units(self, desired_units, no_act=False):
3620  """Set the units of the application of this operator.
3621 
3622  This mixin is used for the <times> and <divide> operators.
3623  There are 2 possible strategies here. One is to pick one of the
3624  operands and convert it so that the overall units match those
3625  required. The other is to pick units from the set of those
3626  possible for this application, and convert the result to the
3627  desired units. We go with the latter option, picking the units
3628  that are least in the sense that they have the least multipicative
3629  factor, but where possible that factor is no less than that on the
3630  desired units.
3631  """
3632  app = self.xml_parent
3633  min_factor, best_factor = None, None
3634  least_units, best_units = None, None
3635  desired_factor = desired_units.expand().get_multiplicative_factor()
3636  DEBUG('validator', '>',self.localName,':',desired_factor,
3637  desired_units.description())
3638  units_set = app.get_units().get_consistent_set(desired_units)
3639  for possible_units in units_set:
3640  f = possible_units.expand().get_multiplicative_factor()
3641  if min_factor is None or f<min_factor:
3642  least_units, min_factor = possible_units, f
3643  if f >= desired_factor and (best_factor is None or f < best_factor):
3644  best_units, best_factor = possible_units, f
3645  if best_units is None:
3646  # All factors were less than that desired, so just go with the least
3647  units = least_units
3648  else:
3649  units = best_units
3650  DEBUG('validator', '\t<-',
3651  units.expand().get_multiplicative_factor(),
3652  units.description())
3653  # Add units conversion code
3654  app._add_units_conversion(app, units, desired_units, no_act)
3655  # Set the operand units
3656  for src_units_set, src_units in app.get_units()._get_sources(units):
3657  expr = src_units_set.get_expression()
3658  DEBUG('validator', '#',self.localName,':',
3659  src_units.description(),expr.localName)
3660  self._set_element_in_units(expr, src_units, no_act)
3661  # Record which units we used
3662  if not no_act:
3663  app._cml_units = units
3664  return
3665 
3667  def _set_in_units(self, units, no_act=False):
3668  """Set the units of this element.
3669 
3670  For container elements, we set the units of the child(ren).
3671  """
3672  # We need to copy the children list, because the tree
3673  # may be modified if a conversion is thought to be needed
3674  for elt in self.xml_children[:]:
3675  if getattr(elt, 'nodeType', None) == Node.ELEMENT_NODE:
3676  self._set_element_in_units(elt, units, no_act)
3677  # And set our units to what they now are
3678  if not no_act:
3679  self._cml_units = units
3680  return
3681 
3683  """Base class for MathML elements."""
3684  def __init__(self):
3685  super(mathml, self).__init__()
3686  self._cml_component = None
3687  self._cml_model = None
3689  return
3690 
3691  def __repr__(self):
3692  return '<%s (%s) at 0x%x>' % (self.__class__.__name__, str(self), id(self))
3693 
3694  def __deepcopy__(self, memo):
3695  """Customise copying of MathML expressions.
3696 
3697  When copying an expression tree, we only want to deepcopy the
3698  XML, not the additional info that we have attached to it -
3699  these should be copied as references to the originals.
3700  """
3701  new_elt = copy.copy(self)
3702  # Children may refer to us, so need to update memo before copying children
3703  assert id(self) not in memo
3704  memo[id(self)] = new_elt
3705  new_dict = {}
3706  for name, value in self.__dict__.iteritems():
3707  name_copy = copy.deepcopy(name, memo)
3708  if not name.startswith('_cml'):
3709  new_dict[name_copy] = copy.deepcopy(value, memo)
3710  else:
3711  new_dict[name_copy] = value
3712  new_elt.__dict__.update(new_dict)
3713  return new_elt
3714 
3715  @staticmethod
3716  def clone(expr):
3717  """Properly clone a MathML sub-expression.
3718 
3719  Makes sure siblings and parent don't get copied too.
3720  """
3721 # print "Cloning MathML", prid(expr, True)
3722  next_elem, par = expr.next_elem, getattr(expr, 'xml_parent', None)
3723  expr.next_elem = None # Make sure we don't copy siblings...
3724  expr.xml_parent = None # ...and parent
3725  new_expr = copy.deepcopy(expr) # Do the clone
3726  expr.next_elem = next_elem # Restore siblings...
3727  expr.xml_parent = par # ...and parent to original
3728  return new_expr
3729 
3730  def clone_self(self, register=False):
3731  """Properly clone this expression.
3732 
3733  If register is True, then keep a link to this expression in the clone.
3734  """
3735  clone = mathml.clone(self)
3736  if register:
3737  clone._cml_source_expr_for_clone = self
3738  return clone
3739 
3741  """
3742  If this is a clone with a registered original expression, return it.
3743  Otherwise returns None.
3744  """
3745  return self._cml_source_expr_for_clone
3746 
3747  def get_component(self):
3748  "Cache & return the enclosing component element."
3749  if self._cml_component is None:
3750  def get_ancestor(elt, name):
3751  while elt and elt.localName != name:
3752  elt = elt.parentNode
3753  return elt
3754  comp = get_ancestor(self, u'component')
3755  if comp:
3756  self._cml_component = comp
3757  else:
3758  # It may be in the solver_info section, in which case fake a component
3759  solver_info = get_ancestor(self, u'solver_info')
3760  if solver_info:
3761  self._cml_component = self.model.get_component_by_name(u'')
3762  else:
3763  raise ValueError("MathML element " + str(self) + " does not occur in a model component!")
3764  return self._cml_component
3765  component = property(get_component)
3766 
3767  def _unset_cached_links(self, elt=None):
3768  """Forget cached component and variable references in this MathML tree.
3769 
3770  Used by partial evaluator when moving maths to a new component, and by
3771  simulation protocols.
3772  """
3773  if elt is None:
3774  elt = self
3775  if isinstance(elt, mathml):
3776  elt._cml_component = None
3777  for child in self.xml_element_children(elt):
3778  if hasattr(child, '_unset_cached_links'):
3779  child._unset_cached_links()
3780  else:
3781  self._unset_cached_links(child)
3782  return
3783 
3784  @property
3785  def model(self):
3786  """Cache & return the enclosing model element."""
3787  if self._cml_model is None:
3788  self._cml_model = self.rootNode.model
3789  return self._cml_model
3790 
3791  def eval(self, elt):
3792  """Evaluate the given element.
3793 
3794  Tries to evaluate the given element, and raises an EvaluationError
3795  if this is not possible.
3796  """
3797  if hasattr(elt, 'evaluate') and callable(elt.evaluate):
3798  return elt.evaluate()
3799  elif elt.localName == u'pi':
3800  return math.pi
3801  else:
3802  # No evaluate() method on elt
3803  raise EvaluationError("Don't know how to evaluate element " +
3804  elt.localName)
3805  return
3806 
3807  def _ensure_units_exist(self, units=None, no_act=False):
3808  """Ensure that there is an element in the XML tree giving this
3809  expression's units.
3810 
3811  Add a new <units> element if this expression has generated units.
3812 
3813  If units is not None, use the given units rather than those of
3814  this expression.
3815 
3816  Return an attribute dictionary with the appropriate units
3817  attribute."""
3818  if no_act:
3819  # Doesn't matter what we return, as it wont be used
3820  return {(u'cml:units', NSS[u'cml']): u'#UNUSED#'}
3821  try:
3822  if units is None:
3823  units = self.get_units().extract()
3824 ## _u = units
3825  units = self.model._get_units_obj(units)
3826  if units._cml_generated and units.name[:3] == "___":
3827 ## print "Adding",units.name, hash(units), units.description(),
3828 ## print "(was",id(_u),"now",id(units),")"
3829  # Ensure referenced units exist
3830  for unit in getattr(units, u'unit', []):
3831  self._ensure_units_exist(unit.get_units_element())
3832  unit.units = unit.get_units_element().name
3833  # Rename units and add to XML tree
3834  msg = "Adding units " + units.name + " as "
3835  units.name = units.description(cellml=True)
3836  msg = msg + units.name
3837  DEBUG('partial-evaluator', msg.encode('UTF-8'))
3838  if units.name == units.unit.units:
3839  # Uh-oh
3840  DEBUG('partial-evaluator', 'Generated units',
3841  units.name, 'identical to referenced units; ignoring.')
3842  assert units.get_multiplicative_factor() == 1
3843  assert units.get_offset() == 0
3844  else:
3845  units.xml_parent.add_units(units.name, units)
3846  units.xml_parent.xml_append(units)
3847  attrs = {(u'cml:units', NSS[u'cml']): units.name}
3848  except UnitsError:
3849  # Hack to allow PE on broken (wrt units) models
3850  attrs = {(u'cml:units', NSS[u'cml']): u'#FUDGE#'}
3851  return attrs
3852 
3853  def varobj(self, ci_elt):
3854  """Return the variable object for the given ci element.
3855 
3856  This method is more general than ci_elt.variable, working even
3857  for ci elements outside of a component. Such elements *must*
3858  contain a fully qualified variable name, i.e. including the
3859  name of the component the variable occurs in. This method
3860  handles a variety of encodings of variable names that contain
3861  the component name.
3862  """
3863  try:
3864  var = ci_elt.variable
3865  except:
3866  varname = unicode(ci_elt).strip()
3867  var = cellml_variable.get_variable_object(self.model, varname)
3868  return var
3869 
3870  def vars_in(self, expr):
3871  """Return a list of 'variable' objects used in the given expression.
3872 
3873  This method doesn't make use of the dependency information
3874  generated when validating the model, but parses the
3875  mathematics afresh. It is used to refresh the dependency
3876  lists after partial evaluation, and to determine dependencies
3877  in mathematics added outside the normal model structure
3878  (e.g. Jacobian calculation).
3879 
3880  If an ODE appears, includes the mathml_apply instance defining
3881  the ODE. Otherwise all returned objects will be
3882  cellml_variable instances.
3883  """
3884  res = set()
3885  if isinstance(expr, mathml_ci):
3886  res.add(self.varobj(expr))
3887  elif isinstance(expr, mathml_apply) and \
3888  expr.operator().localName == u'diff':
3889  dep_var = self.varobj(expr.ci)
3890  indep_var = self.varobj(expr.bvar.ci)
3891  res.add(dep_var.get_ode_dependency(indep_var))
3892  elif hasattr(expr, 'xml_children'):
3893  for child in expr.xml_children:
3894  res.update(self.vars_in(child))
3895  return res
3896 
3897  def same_tree(self, other, this=None):
3898  """Check whether this element represents the same tree as a given element."""
3899  if this is None: this = self
3900  equal = (this.localName == other.localName
3901  and len(getattr(this, 'xml_children', [])) == len(getattr(other, 'xml_children', [])))
3902  if equal and this.localName in [u'cn', u'ci']:
3903  equal = unicode(this) == unicode(other)
3904  if equal and hasattr(this, 'xml_children'):
3905  for tc, oc in zip(self.xml_element_children(this), self.xml_element_children(other)):
3906  if not self.same_tree(oc, tc):
3907  equal = False
3908  break
3909  return equal
3910 
3911  def _xfer_complexity(self, new_elt):
3912  """Transfer our complexity to the new element.
3913 
3914  PE is replacing us by a new element. If we are annotated with
3915  a complexity - the complexity of this expression prior to PE -
3916  then transfer the annotation to the new element.
3917  """
3918  try:
3919  new_elt._cml_complexity = self._cml_complexity
3920  except AttributeError:
3921  pass
3922  return
3923  def _adjust_complexity(self, old_elt, new_elt):
3924  """Adjust ancestor complexity because old_elt changed to new_elt.
3925 
3926  The purpose of this method is to allow us to keep track of
3927  what the complexity of each expression node *was* prior to PE
3928  being performed. Thus we cannot just re-compute complexities,
3929  but must update values using the original complexities. If a
3930  variable definition is instantiated, then the complexity of
3931  the expression containing the lookup must be adjusted to
3932  reflect the additional expense of evaluating the defining
3933  expression.
3934 
3935  When this method is called, only new_elt is a child of self.
3936  """
3937  #print "Adjusting", element_xpath(self), "due to", element_xpath(old_elt),
3938  #if isinstance(old_elt, mathml_ci):
3939  # print unicode(old_elt)
3940  #else:
3941  # print
3942  try:
3943  new = new_elt._cml_complexity
3944  old = old_elt._cml_complexity
3945  except AttributeError:
3946  return
3947  #print " by", new-old
3948  elt = self
3949  while elt:
3950  if isinstance(elt, mathml_piecewise):
3951  # Piecewise is tricky to adjust! So we 're-compute' instead.
3952  ac, piece_ac = 0, []
3953  for piece in getattr(elt, u'piece', []):
3954  ac += child_i(piece, 2)._cml_complexity
3955  piece_ac.append(child_i(piece, 1)._cml_complexity)
3956  if hasattr(elt, u'otherwise'):
3957  piece_ac.append(child_i(elt.otherwise, 1)._cml_complexity)
3958  ac += max(piece_ac)
3959  elt._cml_complexity = ac
3960  elif hasattr(elt, '_cml_complexity'):
3961  elt._cml_complexity += (new - old)
3962  elt = getattr(elt, 'xml_parent', None)
3963  return
3964 
3965  def classify_child_variables(self, elt, **kwargs):
3966  """Classify variables in the given expression according to how they are used.
3967 
3968  In the process, compute and return a set of variables on which that expression depends.
3969 
3970  If dependencies_only then the variable classification will not be
3971  done, only dependencies will be analysed. This is useful for doing
3972  a 'light' re-analysis if the dependency set has been reduced; if the
3973  set has increased then the topological sort of equations may need to
3974  be redone.
3975 
3976  The function needs_special_treatment may be supplied to override the
3977  default recursion into sub-trees. It takes a single sub-tree as
3978  argument, and should either return the dependency set for that
3979  sub-tree, or None to use the default recursion. This is used when
3980  re-analysing dependencies after applying lookup tables, since table
3981  lookups only depend on the keying variable.
3982  """
3983  if hasattr(elt, 'classify_variables'):
3984  dependencies = elt.classify_variables(**kwargs)
3985  else:
3986  dependencies = set()
3987  needs_special_treatment = kwargs.get('needs_special_treatment', lambda e: None)
3988  for e in elt.xml_element_children():
3989  child_deps = needs_special_treatment(e)
3990  if child_deps is None:
3991  child_deps = self.classify_child_variables(e, **kwargs)
3992  dependencies.update(child_deps)
3993  return dependencies
3994 
3996  def __init__(self):
3997  super(mathml_math, self).__init__()
3998  return
3999 
4001  """
4002  Base class for MathML constructor elements, e.g. apply and piecewise.
4003  """
4004  def __init__(self):
4005  super(mathml_constructor, self).__init__()
4006  return
4007 
4008  def _tree_complexity(self, elt, **kw):
4009  """
4010  Calculate a rough estimate of the computation time for
4011  evaluating the given element.
4012 
4013  If lookup_tables is True, then assume we're using lookup tables
4014  where possible.
4015  If store_result is True, the complexity is saved to the
4016  _cml_complexity attribute.
4017  If algebraic is True, the complexity is calculated as a dictionary,
4018  mapping node types to the number of occurences of that type.
4019  """
4020  kw['lookup_tables'] = kw.get('lookup_tables', False)
4021  kw['store_result'] = kw.get('store_result', False)
4022  kw['algebraic'] = kw.get('algebraic', False)
4023  if kw['algebraic']:
4024  ac = {}
4025  if kw['lookup_tables'] and \
4026  elt.getAttributeNS(NSS['lut'], u'possible', u'no') == u'yes':
4027  # This elt will be replaced by a lookup table
4028  if hasattr(self.rootNode, 'num_lookup_tables'):
4029  # Count the number of used lookup tables
4030  self.rootNode.num_lookup_tables += 1
4031  # Cost per table: 2 lookups, 2 +, -, *, 3 ci
4032  if kw['algebraic']:
4033  ac['lookup'] = 2
4034  ac['op'] = 3
4035  ac['times'] = 1
4036  ac['variable'] = 3
4037  else:
4038  ac = 2*1 + 2*1 + 1 + 1 + 3*0.7
4039  elif hasattr(elt, 'tree_complexity') \
4040  and callable(elt.tree_complexity):
4041  ac = elt.tree_complexity(**kw)
4042  elif elt.localName in ['true', 'false', 'cn', 'exponentiale', 'pi']:
4043  if kw['algebraic']: ac['constant'] = 1
4044  else: ac = 0.5
4045  elif elt.localName == 'ci':
4046  if kw['algebraic']: ac['variable'] = 1
4047  else: ac = 0.7
4048  elif elt.localName in ['degree', 'logbase']:
4049  ac = self._tree_complexity(child_i(elt, 1), **kw)
4050  else:
4051  raise EvaluationError("Don't know complexity of element " +
4052  elt.localName)
4053  if kw['store_result'] and isinstance(elt, mathml):
4054  elt._cml_complexity = ac
4055  return ac
4056 
4058  """Helper method to get the binding time of a MathML element."""
4059  if hasattr(elt, '_get_binding_time'):
4060  # Call the element's method
4061  return elt._get_binding_time()
4062  elif elt.localName in [u'true', u'false', u'exponentiale', u'pi']:
4063  return BINDING_TIMES.static
4064  else:
4065  raise EvaluationError("Don't know how to compute binding time"
4066  " of element " + elt.localName)
4067 
4068  def _get_element_units(self, elt, return_set=True):
4069  """Helper method to get the units of a MathML element."""
4070  if hasattr(elt, 'get_units'):
4071  # Element has a method to get its units, so call it
4072  u = elt.get_units()
4073  elif hasattr(elt, '_cml_units') and elt._cml_units:
4074  # Units have been cached
4075  u = elt._cml_units
4076  else:
4077  # Let's figure it out ourselves...
4078  if elt.localName in [u'false', u'true']:
4079  u = UnitsSet(
4080  [self.component.get_units_by_name('cellml:boolean')],
4081  expression=elt)
4082  elif elt.localName in [u'notanumber', u'pi', u'infinity',
4083  u'exponentiale']:
4084  u = UnitsSet(
4085  [self.component.get_units_by_name('dimensionless')],
4086  expression=elt)
4087  else:
4088  # Unknown or unexpected element
4089  raise UnitsError(self, u''.join([
4090  u'Unsupported element "', elt.localName, '".']))
4091  if not return_set:
4092  u = u.extract()
4093  return u
4094 
4095  def _reduce_elt(self, elt):
4096  """Try to reduce the given element.
4097 
4098  Call the _reduce method on elt, if it has one.
4099  If not, do nothing (we assume elt cannot be reduced).
4100  """
4101  if hasattr(elt, '_reduce') and callable(elt._reduce):
4102  elt._reduce()
4103  else:
4104  DEBUG('partial-evaluator', "Don't know how to reduce",
4105  elt.localName)
4106  return
4107 
4108  def _eval_self(self):
4109  """Evaluate self and return <cn>, <true> or <false>, as appropriate."""
4110  value = self.evaluate()
4111  if value is True:
4112  new_elt = self.xml_create_element(u'true', NSS[u'm'])
4113  elif value is False:
4114  new_elt = self.xml_create_element(u'false', NSS[u'm'])
4115  else:
4116  # Add a new <units> element to the document if needed
4117  attrs = self._ensure_units_exist()
4118  new_elt = self.xml_create_element(u'cn', NSS[u'm'],
4119  content=unicode("%.17g" % value),
4120  attributes=attrs)
4121  return new_elt
4122 
4123  def _update_usage_counts(self, expr, remove=False):
4124  """Update usage counts of variables used in the given expression.
4125 
4126  By default, increment the usage count of any variable occuring
4127  in a <ci> element within expr. If remove is set to True,
4128  then decrement the usage counts instead.
4129  """
4130  if isinstance(expr, mathml_ci):
4131  if remove:
4132  expr.variable._decrement_usage_count()
4133  else:
4134  raise NotImplementedError("_update_usage_counts currently only reliable for remove=True")
4135  expr.variable._used()
4136  elif isinstance(expr, mathml_apply) and isinstance(expr.operator(),
4137  mathml_diff):
4138  # TODO: Check if this is a suitable handling of ODEs on a RHS
4139  # It matches the current behaviour in apply.classify_variables.
4140  pass
4141  else:
4142  for e in self.xml_element_children(expr):
4143  self._update_usage_counts(e, remove=remove)
4144 
4146  def __init__(self):
4147  super(mathml_cn, self).__init__()
4148  self._cml_units = None
4149  return
4150 
4151  def evaluate(self):
4152  """
4153  Convert the text content of this element to a floating point
4154  value and return it. Will handle the type attribute and, if
4155  relevant to the type, the sep child element, but does not yet
4156  handle the base attribute.
4157  """
4158  if hasattr(self, u'base'):
4159  raise ValueError('pycml does not yet support the base attribute on cn elements')
4160  if hasattr(self, u'type'):
4161  if self.type == u'real':
4162  val = float(unicode(self))
4163  elif self.type == u'integer':
4164  val = int(unicode(self))
4165  elif self.type == u'e-notation':
4166  assert len(self.xml_children) == 3
4167  assert self.xml_children[1] is self.sep
4168  mantissa = unicode(self.xml_children[0]).strip()
4169  exponent = unicode(self.xml_children[2]).strip()
4170  val = float(mantissa + 'e' + exponent)
4171  elif self.type == u'rational':
4172  assert len(self.xml_children) == 3
4173  assert self.xml_children[1] is self.sep
4174  numer = int(unicode(self.xml_children[0]))
4175  denom = int(unicode(self.xml_children[2]))
4176  val = numer / denom
4177  else:
4178  raise ValueError('Unsupported type attribute for cn element: '
4179  + self.type)
4180  else:
4181  val = float(unicode(self))
4182  return val
4183 
4185  """Return the binding time of this expression.
4186 
4187  The binding time of a <cn> element is always static,
4188  unless the CellML is annotated otherwise.
4189  """
4190  bt = self.getAttributeNS(NSS['pe'], u'binding_time', u'static')
4191  return getattr(BINDING_TIMES, bt)
4192 
4193  def _reduce(self):
4194  """Reduce this expression by evaluating its static parts.
4195 
4196  Is actually a no-op; we must have been annotated explicitly as dynamic.
4197  """
4198  return
4199 
4200  def get_units(self, return_set=True):
4201  """Return the units this number is expressed in."""
4202  if not self._cml_units:
4203  self._cml_units = UnitsSet([self.component.get_units_by_name(self.units)], expression=self)
4204  if not return_set:
4205  u = self._cml_units.extract()
4206  else:
4207  u = self._cml_units
4208  return u
4209 
4210  @staticmethod
4211  def create_new(elt, value, units):
4212  """Create a new <cn> element with the given value and units."""
4213  attrs = {(u'cml:units', NSS[u'cml']): unicode(units)}
4214  new_elt = elt.xml_create_element(u'cn', NSS[u'm'],
4215  attributes=attrs,
4216  content=unicode(value))
4217  return new_elt
4218 
4220  def __init__(self):
4221  super(mathml_ci, self).__init__()
4222  self._cml_variable = None
4223  self._cml_units = None
4224  return
4225 
4226  def _unset_cached_links(self, elt=None):
4227  """Forget cached component and variable references in this MathML tree.
4228 
4229  Used by partial evaluator when moving maths to a new component, and by
4230  simulation protocols.
4231  """
4232  self._cml_variable = None
4233  super(mathml_ci, self)._unset_cached_links()
4234 
4235  @property
4236  def variable(self):
4237  """Cache & return the variable object refered to by this element."""
4238  if self._cml_variable is None:
4239  vname = unicode(self).strip()
4240  self._rename(vname) # Remove excess whitespace from our text content
4241  self._cml_variable = self.component.get_variable_by_name(vname)
4242  return self._cml_variable
4243 
4244  def _set_variable_obj(self, var):
4245  """Set the variable object referred to by this element."""
4246  self._cml_variable = var
4247 
4248  def get_units(self, return_set=True):
4249  """Return the units of the variable represented by this element."""
4250  if not self._cml_units:
4251  self._cml_units = UnitsSet([self.component.get_units_by_name(self.variable.units)],
4252  expression=self)
4253  if not return_set:
4254  u = self._cml_units.extract()
4255  else:
4256  u = self._cml_units
4257  return u
4258 
4259  def evaluate(self):
4260  """
4261  Evaluate this expression by returning the value of the
4262  variable it represents.
4263  """
4264  return self.variable.get_value()
4265 
4267  """Return the binding time of this expression.
4268 
4269  The binding time of a <ci> element is that of the variable it
4270  represents.
4271  """
4272  return self.variable._get_binding_time()
4273 
4274  def _rename(self, new_name=None):
4275  """Update the variable reference to use a canonical name."""
4276  self.xml_remove_child(unicode(self))
4277  if new_name is None:
4278  new_name = self.variable.fullname(cellml=True)
4279  self.xml_append(unicode(new_name))
4280  return
4281 
4282  def _reduce(self):
4283  """Reduce this expression by evaluating its static parts.
4284 
4285  If this is a static variable, replace by its value (as a <cn> element).
4286 
4287  Otherwise the behaviour depends on the number of uses of this
4288  variable. If there is only one, instantiate the definition of
4289  this variable here in place of the <ci> element, otherwise
4290  leave the element unchanged to avoid code duplication.
4291  """
4292  bt = self._get_binding_time()
4293  DEBUG('partial-evaluator', "Reducing", self.variable.fullname(),
4294  "which is", bt)
4295  if bt is BINDING_TIMES.static:
4296  value = self.evaluate()
4297  attrs = {(u'cml:units', NSS[u'cml']): self.variable.units}
4298  cn = self.xml_create_element(u'cn', NSS[u'm'],
4299  content=unicode("%.17g" % value),
4300  attributes=attrs)
4301  DEBUG('partial-evaluator', " value =", unicode(cn))
4302  self._xfer_complexity(cn)
4303  self.xml_parent.xml_insert_after(self, cn)
4304  self.xml_parent.xml_remove_child(self)
4305  self.variable._decrement_usage_count()
4306  else:
4307  defns = self.variable.get_dependencies()
4308  if defns:
4309  defn = defns[0]
4310  else:
4311  # Just need to update the name to be canonical - done in later pass
4312  defn = None
4313  if isinstance(defn, cellml_variable):
4314  if self.variable.pe_keep:
4315  # Don't remove this var, just reduce its source
4316  DEBUG('partial-evaluator', "Keeping",
4317  self.variable.fullname())
4318  self.variable._reduce(update_usage=True)
4319  else:
4320  # Create a new <ci> element
4321  ci = self.xml_create_element(
4322  u'ci', NSS[u'm'], content=defn.fullname(cellml=True))
4323  self._xfer_complexity(ci)
4324  ci._set_variable_obj(defn)
4325  DEBUG('partial-evaluator', " to", defn.fullname())
4326  self.xml_parent.xml_insert_after(self, ci)
4327  self.xml_parent.xml_remove_child(self)
4328  # Decrement the usage count of just us, not source vars
4329  self.variable._decrement_usage_count(follow_maps=False)
4330  # May need to recurse down maps
4331  ci._reduce()
4332  elif isinstance(defn, mathml_apply):
4333  if (not self.variable.pe_keep and
4334  (self.variable.get_usage_count() == 1 or
4335  self.rootNode.partial_evaluator.is_instantiable(defn))):
4336  # defn is defining expression, so will be a MathML element already,
4337  # and should be reduced already as well due to topological sort.
4338  # Clone the RHS and instantiate it here.
4339  rhs = mathml.clone(list(defn.operands())[1])
4340  DEBUG('partial-evaluator', " to", rhs)
4341  parent = self.xml_parent
4342  parent.xml_insert_after(self, rhs)
4343  parent.xml_remove_child(self)
4344  parent._adjust_complexity(self, rhs)
4345  # Flag the defining expression for removal
4346  defn._pe_process = u'remove'
4347  self.variable._decrement_usage_count()
4348  elif defn is not None:
4349  raise ValueError("Unexpected variable definition: " + defn.xml())
4350  return
4351 
4352  def classify_variables(self, dependencies_only=False,
4353  needs_special_treatment=lambda n: None):
4354  """Classify variables in this expression according to how they are used.
4355 
4356  For ci elements we just return a set containing the referenced variable
4357  as the single dependency. If dependencies_only is False, we also mark
4358  the variable as used.
4359  """
4360  var = self.variable
4361  if not dependencies_only:
4362  var._used()
4363  return set([var])
4364 
4365  @staticmethod
4366  def create_new(elt, variable_name):
4367  """Create a new <ci> element with the given variable name."""
4368  new_elt = elt.xml_create_element(u'ci', NSS[u'm'],
4369  content=unicode(variable_name))
4370  return new_elt
4371 
4373 
4374  QUALIFIERS = frozenset(('degree', 'bvar', 'logbase',
4375  'lowlimit', 'uplimit', 'interval', 'condition',
4376  'domainofapplication', 'momentabout'))
4377 
4378  class OPS:
4379  """Classifications of operators."""
4380  absRound = frozenset(('abs', 'floor', 'ceiling', 'rem'))
4381  timesDivide = frozenset(('times', 'divide'))
4382  plusMinus = frozenset(('plus', 'minus'))
4383  trig = frozenset(('sin', 'cos', 'tan', 'sec', 'csc', 'cot',
4384  'sinh', 'cosh', 'tanh', 'sech', 'csch', 'coth',
4385  'arcsin', 'arccos', 'arctan',
4386  'arcsec', 'arccsc', 'arccot',
4387  'arcsinh', 'arccosh', 'arctanh',
4388  'arcsech', 'arccsch', 'arccoth'))
4389  elementary = frozenset(('exp', 'log', 'ln')).union(trig)
4390  relations = frozenset(('eq', 'neq', 'gt', 'lt', 'geq', 'leq'))
4391  logical = frozenset(('and', 'or', 'xor', 'not'))
4392 
4393  def __init__(self):
4394  super(mathml_apply, self).__init__()
4395  self._cml_units = None
4396  self.clear_dependency_info()
4397 
4399  """Clear the type, dependency, etc. information for this equation.
4400 
4401  This allows us to re-run the type & dependency analysis for the model."""
4402  self._cml_binding_time = None
4403  # Dependency graph edges
4405  self._cml_assigns_to = None
4406  self.clear_colour()
4407 
4408  def get_dependencies(self):
4409  """Return the list of variables this expression depends on."""
4410  return self._cml_depends_on
4411 
4412  def operator(self):
4413  """Return the element representing the operator being applied."""
4414  return _child1(self)
4415 
4416  def _is_qualifier(self, element):
4417  """Return True iff element is a qualifier element."""
4418  return element.localName in self.QUALIFIERS
4419 
4420  def qualifiers(self):
4421  """
4422  Return an iterable over the elements representing the qualifiers for
4423  this application.
4424  """
4425  quals = self.xml_element_children()
4426  return filter(self._is_qualifier, quals)
4427 
4428  def operands(self):
4429  """
4430  Return an iterable over the elements representing the operands for
4431  this application.
4432  """
4433  # Get all element children and strip the first (the operator)
4434  operands = self.xml_element_children()
4435  operands.next()
4436  # Now strip qualifiers from the front
4437  return itertools.dropwhile(self._is_qualifier, operands)
4438 
4440  """
4441  Return an iterable containing a <units> element for each operand
4442  of this expression.
4443  """
4444  for o in self.operands():
4445  yield self._get_element_units(o)
4446 
4447  def evaluate(self):
4448  """
4449  Evaluate this expression, and return its value, if possible.
4450  """
4451  # Result depends on the operator
4452  op = self.operator()
4453  if hasattr(op, 'evaluate') and callable(op.evaluate):
4454  return op.evaluate()
4455  else:
4456  raise EvaluationError("Don't know how to evaluate the operator " + op.localName)
4457 
4458  def tree_complexity(self, **kw):
4459  """
4460  Calculate a rough estimate of the computation time for
4461  evaluating this <apply> element.
4462 
4463  Operates recursively, so the complexity of a function call is
4464  given by summing the complexity of the arguments and the time
4465  for evaluating the function itself.
4466 
4467  If lookup_tables is True, then assume we're using lookup tables
4468  where possible.
4469  If algebraic is True, the complexity is calculated as a dictionary,
4470  mapping node types to the number of occurences of that type.
4471  """
4472  kw['algebraic'] = kw.get('algebraic', False)
4473  if kw['algebraic']: ac = {}
4474  else: ac = 0
4475 
4476  # Complexity of this function
4477  op_name = self.operator().localName
4478  OPS = self.OPS
4479  if op_name in OPS.plusMinus or op_name in OPS.logical or op_name in OPS.relations:
4480  if kw['algebraic']: ac['op'] = (len(list(self.operands())) - 1)
4481  else: ac += 1 * (len(list(self.operands())) - 1)
4482  elif op_name in OPS.absRound:
4483  if op_name == 'abs':
4484  if kw['algebraic']: ac['abs'] = 1
4485  else: ac += 5
4486  else:
4487  if kw['algebraic']: ac['round'] = 1
4488  else: ac += 20
4489  elif op_name in OPS.elementary:
4490  if kw['algebraic']: ac['elementary'] = 1
4491  else: ac += 70
4492  elif op_name == 'times':
4493  if kw['algebraic']: ac['times'] = (len(list(self.operands())) - 1)
4494  else: ac += 1 * (len(list(self.operands())) - 1)
4495  elif op_name == 'divide':
4496  if kw['algebraic']: ac['divide'] = 1
4497  else: ac += 15
4498  elif op_name == 'power':
4499  # This will vary depending on the exponent - gcc can optimise for 2 and 3, it seems.
4500  exponent = list(self.operands())[1]
4501  if exponent.localName == u'cn' and unicode(exponent).strip() in [u'2', u'3']:
4502  if kw['algebraic']: ac['power2'] = 1
4503  else: ac += 5
4504  else:
4505  if kw['algebraic']: ac['power'] = 1
4506  else: ac += 30
4507  elif op_name == 'root':
4508  if kw['algebraic']: ac['root'] = 1
4509  else: ac += 30
4510  elif op_name == 'diff':
4511  if kw['algebraic']: ac['variable'] = 1
4512  else: ac += 0.7
4513  else:
4514  raise EvaluationError("Don't know complexity of operator " + op_name)
4515 
4516  # Complexity of operands
4517  for elt in self.operands():
4518  if kw['algebraic']:
4519  add_dicts(ac, self._tree_complexity(elt, **kw))
4520  else: ac += self._tree_complexity(elt, **kw)
4521  return ac
4522 
4523  def _set_in_units(self, units, no_act=False):
4524  """Set the units this expression should be given in.
4525 
4526  If these aren't our natural units (as given by an initial
4527  get_units) then we need to add units conversion code.
4528  """
4529  # First check we have something to do
4530  current_units = self.get_units()
4531  if units is current_units:
4532  return
4533  # Next, check if the required units can be achieved by suitable choices for operand units
4534  done = False
4535  if units in current_units:
4536  # They can!
4537  if not no_act:
4538  self._cml_units = units
4539  for src_units_set, src_units in current_units._get_sources(units):
4540  expr = src_units_set.get_expression()
4541  self._set_element_in_units(expr, src_units, no_act)
4542  done = True
4543  if not done and not no_act:
4544  # Some operators need this to be a UnitsSet
4545  self._cml_units = UnitsSet([units], self)
4546  if not done:
4547  # The behaviour now depends on the operator
4548  op = self.operator()
4549  if hasattr(op, '_set_in_units') and callable(op._set_in_units):
4550  op._set_in_units(units, no_act)
4551  else:
4552  raise UnitsError(self, u' '.join([
4553  "Don't know how to select units for operands of operator",
4554  op.localName, "when its units are", units.description()]))
4555 
4556  def get_units(self):
4557  """Recursively check this expression for dimensional consistency.
4558 
4559  Checks that the operands have suitable units.
4560  What constitutes 'suitable' depends on the operator; see appendix
4561  C.3.2 of the CellML 1.0 spec.
4562 
4563  If yes, returns a <units> element for the whole expression, based
4564  on the rules in appendix C.3.3.
4565 
4566  Throws a UnitsError if the units are inconsistent.
4567  """
4568  if self._cml_units:
4569  return self._cml_units
4570  our_units = None
4571  op = self.operator().localName
4572  operand_units = self._get_operand_units()
4573  # Tuples where second item is an index
4574  operand_units_idx = itertools.izip(operand_units, itertools.count(1))
4575  # Standard units objects
4576  dimensionless = self.model.get_units_by_name(u'dimensionless')
4577  boolean = self.model.get_units_by_name(u'cellml:boolean')
4578 
4579  if op in self.OPS.relations | self.OPS.plusMinus:
4580  our_units = operand_units.next().copy()
4581  # Operands mustn't be booleans
4582  if boolean in our_units:
4583  raise UnitsError(self, u' '.join([
4584  u'Operator',op,u'has boolean operands, which does not make sense.']))
4585  # Operand units must be 'equivalent' (perhaps dimensionally)
4586  for u in operand_units:
4587  if not hasattr(self.model, '_cml_special_units_converter') and not our_units.dimensionally_equivalent(u):
4588  raise UnitsError(self, u' '.join([
4589  u'Operator',op,u'requires its operands to have',
4590  u'dimensionally equivalent units;',u.description(),
4591  u'and',our_units.description(),u'differ']))
4592  our_units.update(u)
4593  if op in self.OPS.relations:
4594  # Result has cellml:boolean units
4595  our_units = UnitsSet([boolean])
4596  elif op in self.OPS.logical:
4597  # Operand units must be cellml:boolean
4598  for u, i in operand_units_idx:
4599  if not boolean in u:
4600  raise UnitsError(self, u' '.join([
4601  u'Operator',op,u'requires operands to be booleans;',
4602  u'operand',str(i),u'has units',u.description()]))
4603  # Result has cellml:boolean units
4604  our_units = UnitsSet([boolean])
4605  elif op in self.OPS.elementary:
4606  # Operand units must be dimensionless
4607  for u, i in operand_units_idx:
4608  if not u.dimensionally_equivalent(dimensionless):
4609  raise UnitsError(self, u' '.join([
4610  u'Operator',op,u'requires operands to be dimensionless;',
4611  u'operand',str(i),u'has units',u.description()]))
4612  if op == 'log':
4613  # <logbase> qualifier must have units dimensionless
4614  if hasattr(self, u'logbase'):
4615  base = _child1(self.logbase)
4616  u = self._get_element_units(base)
4617  if not u.dimensionally_equivalent(dimensionless):
4618  raise UnitsError(self, u' '.join([u'The logbase qualifier must have dimensionless',
4619  u'units, not',u.description()]))
4620  # Result has units of dimensionless
4621  our_units = UnitsSet([dimensionless])
4622  elif op == 'power':
4623  # Arg1 : any, Arg2 : dimensionless
4624  arg_units = operand_units.next()
4625  if boolean in arg_units:
4626  raise UnitsError(self, u'The argument of <power> should not be boolean')
4627  exponent_units = operand_units.next()
4628  if not exponent_units.dimensionally_equivalent(dimensionless):
4629  raise UnitsError(self, u' '.join([u'The second operand to power must have dimensionless',
4630  u'units, not',exponent_units.description()]))
4631  # Result has units that are the units on the (first)
4632  # operand raised to the power of the second operand. If
4633  # units on the first operand are dimensionless, then so is
4634  # the result.
4635  # TODO: Check how we could allow equiv. to d'less, instead of equal.
4636  # Need to consider any multiplicative factor...
4637  if arg_units.equals(dimensionless):
4638  our_units = UnitsSet([dimensionless])
4639  else:
4640  opers = self.operands()
4641  opers.next()
4642  # Make sure exponent is static
4643  expt = opers.next()
4644  if self._get_element_binding_time(expt) != BINDING_TIMES.static:
4645  raise UnitsError(self, 'Unable to units check power with an exponent that can vary at run-time',
4646  warn=True,
4647  level=logging.WARNING_TRANSLATE_ERROR)
4648  # Try to evaluate the exponent
4649  try:
4650  expt = self.eval(expt)
4651  except EvaluationError, e:
4652  raise UnitsError(self, u' '.join([u'Unable to evaluate the exponent of a power element:', unicode(e)]),
4653  warn=True,
4654  level=logging.WARNING_TRANSLATE_ERROR)
4655  our_units = dimensionless.simplify(arg_units, expt)
4656  elif op == 'root':
4657  # Arg : any, <degree> : dimensionless
4658  arg_units = operand_units.next()
4659  if boolean in arg_units:
4660  raise UnitsError(self, u'The argument of <root> should not be boolean')
4661  if hasattr(self, u'degree'):
4662  degree = _child1(self.degree)
4663  u = self._get_element_units(degree)
4664  if not u.dimensionally_equivalent(dimensionless):
4665  raise UnitsError(self, u' '.join([
4666  u'The degree qualifier must have dimensionless units, not',u.description()]))
4667  else:
4668  degree = 2.0 # Default is square root
4669  # Result has units that are the units on the (first) operand
4670  # raised to the power of the reciprocal of the value of the
4671  # degree qualifier.
4672  # TODO: If units on the first operand are dimensionless,
4673  # then so is the result.
4674  if not type(degree) is float:
4675  # Make sure degree is static
4676  if self._get_element_binding_time(degree) != BINDING_TIMES.static:
4677  raise UnitsError(self, 'Unable to units check root with a degree that can vary at run-time',
4678  warn=True,
4679  level=logging.WARNING_TRANSLATE_ERROR)
4680  try:
4681  degree = self.eval(degree)
4682  except EvaluationError, e:
4683  raise UnitsError(self, u' '.join([u'Unable to evaluate the degree of a root element:', unicode(e)]),
4684  warn=True,
4685  level=logging.WARNING_TRANSLATE_ERROR)
4686  our_units = dimensionless.simplify(arg_units, 1/degree)
4687  elif op == 'diff':
4688  # Arg : any, <bvar> : any, <degree> : dimensionless
4689  arg_units = operand_units.next()
4690  if boolean in arg_units:
4691  raise UnitsError(self, u'The argument of <diff> should not be boolean')
4692  if hasattr(self, u'bvar'):
4693  if hasattr(self.bvar, u'degree'):
4694  degree = _child1(self.bvar.degree)
4695  u = self._get_element_units(degree)
4696  if not u.dimensionally_equivalent(dimensionless):
4697  raise UnitsError(self, u' '.join([
4698  u'The degree qualifier must have dimensionless units, not',u.description()]))
4699  else:
4700  degree = 1.0 # Default is first derivative
4701  else:
4702  raise UnitsError(self, u'A diff operator must have a bvar qualifier')
4703  # Result has units that are the quotient of the units of the
4704  # operand, over the units of the term in the bvar qualifier
4705  # raised to the value of the degree qualifier
4706  if not type(degree) is float:
4707  # Make sure exponent is static
4708  if self._get_element_binding_time(degree) != BINDING_TIMES.static:
4709  raise UnitsError(self, 'Unable to units check derivative with a degree that can vary at run-time',
4710  warn=True,
4711  level=logging.WARNING_TRANSLATE_ERROR)
4712  try:
4713  degree = self.eval(degree)
4714  except EvaluationError, e:
4715  raise UnitsError(self, u' '.join([u'Unable to evaluate the degree of a diff element:', unicode(e)]),
4716  warn=True,
4717  level=logging.WARNING_TRANSLATE_ERROR)
4718  for e in self.xml_element_children(self.bvar):
4719  if not e.localName == u'degree':
4720  bvar_units = self._get_element_units(e)
4721  break
4722  else:
4723  raise UnitsError(self, u'diff element does not have a valid bvar')
4724  our_units = arg_units.simplify(bvar_units, -degree)
4725  elif op in self.OPS.absRound | self.OPS.timesDivide:
4726  # No restrictions on operand units, except that they shouldn't be boolean
4727  for u in self._get_operand_units():
4728  if boolean in u:
4729  raise UnitsError(self, u' '.join([
4730  u'Operator',op,u'has boolean operands, which does not make sense.']))
4731  if op == 'times':
4732  # Result has units that are the product of the operand units
4733  our_units = operand_units.next().copy()
4734  for u in operand_units:
4735  our_units = our_units.simplify(other_units=u)
4736  elif op == 'divide':
4737  # Result has units that are the quotient of the units
4738  # on the first and second operands
4739  our_units = operand_units.next()
4740  our_units = our_units.simplify(other_units=operand_units.next(), other_exponent=-1)
4741  else:
4742  # Result has same units as operands
4743  our_units = operand_units.next().copy()
4744  else:
4745  # Warning: unsupported operator!
4746  raise UnitsError(self, u' '.join([u'Unsupported operator for units checking:', op]),
4747  warn=True,
4748  level=logging.WARNING_TRANSLATE_ERROR)
4749 
4750  # Cache & return result
4751  if isinstance(our_units, cellml_units):
4752  # Units conversion has been done, then PE
4753  our_units = UnitsSet([our_units])
4754  self._cml_units = our_units
4755  our_units.set_expression(self)
4756  return self._cml_units
4757 
4759  """Check the current component owns the variable being assigned to.
4760 
4761  Should only be called if this object represents an assignment
4762  expression. Checks that the variable being assigned to doesn't
4763  have an interface value of 'in'. If this isn't a simple assignment
4764  (i.e. the LHS isn't a plain ci element) then the check succeeds
4765  automatically.
4766 
4767  Adds to the model's error list if the check fails. This method
4768  always returns None.
4769  """
4770  # Check this is an application of eq
4771  if self.operator().localName != u'eq':
4772  raise MathsError(self, u'Top-level mathematics expressions should be assigment expressions.')
4773  first_operand = self.operands().next()
4774  if first_operand.localName == u'ci':
4775  # We've already checked that the variable exists
4776  var = first_operand.variable
4777  for iface in [u'public_interface', u'private_interface']:
4778  if getattr(var, iface, u'none') == u'in':
4779  raise MathsError(self, u' '.join([
4780  u'Variable', var.fullname(),
4781  u'is assigned to in a math element, but has its',
4782  iface, u'set to "in".']))
4783 
4784  def classify_variables(self, root=False,
4785  dependencies_only=False,
4786  needs_special_treatment=lambda n: None):
4787  """
4788  Classify variables in this expression according to how they are
4789  used.
4790 
4791  In the process, compute and return a set of variables on which
4792  this expression depends. If root is True, store this set as a
4793  list, to represent edges of a dependency graph.
4794  Also, if root is True then this node is the root of an expression
4795  (so it will be an application of eq); treat the LHS differently.
4796 
4797  If dependencies_only then the variable classification will not be
4798  done, only dependencies will be analysed. This is useful for doing
4799  a 'light' re-analysis if the dependency set has been reduced; if the
4800  set has increased then the topological sort of equations may need to
4801  be redone.
4802 
4803  The function needs_special_treatment may be supplied to override the
4804  default recursion into sub-trees. It takes a single sub-tree as
4805  argument, and should either return the dependency set for that
4806  sub-tree, or None to use the default recursion. This is used when
4807  re-analysing dependencies after applying lookup tables, since table
4808  lookups only depend on the keying variable.
4809  """
4810  dependencies = set()
4811  ode_indep_var = None
4812  op = self.operator()
4813  if op.localName == u'diff':
4814  # This is a derivative dy/dx on the RHS of an assignment.
4815  # Store the dependency as a pair (y,x)
4816  dependencies.add((op.dependent_variable, op.independent_variable))
4817  if not dependencies_only:
4818  # Set variable types
4819  op._set_var_types()
4820  else:
4821  opers = self.operands()
4822  if root:
4823  # Treat the LHS of the assignment
4824  lhs = opers.next()
4825  if lhs.localName == u'ci':
4826  # Direct assignment to variable
4827  var = lhs.variable
4828  var._add_dependency(self)
4829  self._cml_assigns_to = var
4830  if not dependencies_only:
4831  # Check for possibly conflicting types
4832  t = var.get_type()
4833  if t == VarTypes.Constant or t == VarTypes.MaybeConstant:
4834  self.model.validation_warning(
4835  u' '.join([
4836  u'Variable',var.fullname(),u'is assigned to',
4837  u'and has an initial value set.']),
4838  level=logging.WARNING_TRANSLATE_ERROR)
4839  elif t == VarTypes.State or t == VarTypes.Free:
4840  self.model.validation_warning(
4841  u' '.join([
4842  u'Variable',var.fullname(),u'is assigned to',
4843  u'and appears on the LHS of an ODE.']),
4844  level=logging.WARNING_TRANSLATE_ERROR)
4845  var._set_type(VarTypes.Computed)
4846  elif lhs.localName == u'apply':
4847  # This could be an ODE
4848  diff = lhs.operator()
4849  if diff.localName == u'diff':
4850  # It is an ODE. TODO: Record it somewhere?
4851  if not dependencies_only:
4852  diff._set_var_types()
4853  dep = diff.dependent_variable
4854  indep = diff.independent_variable
4855  dep._add_ode_dependency(indep, self)
4856  # An ODE should depend on its independent variable
4857  ode_indep_var = indep
4858  if not dependencies_only:
4859  indep._used()
4860  # TODO: Hack; may remove.
4861  self._cml_assigns_to = (dep, indep)
4862  else:
4863  raise MathsError(self, u'Assignment statements are expected to be an ODE or assign to a variable.',
4864  warn=True,
4865  level=logging.WARNING_TRANSLATE_ERROR)
4866  else:
4867  raise MathsError(self, u'Assignment statements are expected to be an ODE or assign to a variable.',
4868  warn=True,
4869  level=logging.WARNING_TRANSLATE_ERROR)
4870 
4871  # Consider operands other than the LHS of an assignment
4872  for oper in opers:
4873  dependencies.update(self.classify_child_variables(oper, dependencies_only=dependencies_only,
4874  needs_special_treatment=needs_special_treatment))
4875 
4876  if ode_indep_var:
4877  # ODEs should depend on their independent variable.
4878  # However, for code generation we wish to distinguish
4879  # whether the independent variable appears on the RHS or
4880  # not.
4881  if ode_indep_var in dependencies:
4883  else:
4884  self._cml_ode_has_free_var_on_rhs = False
4885  dependencies.add(ode_indep_var)
4886 
4887  if root:
4888  # Store dependencies
4889  self._cml_depends_on = list(dependencies)
4890  return dependencies
4891 
4892  def is_top_level(self):
4893  """Test whether this is a top-level assignment expression."""
4894  return self._cml_assigns_to is not None
4895 
4896  def is_ode(self):
4897  """Return True iff this is the assignment of an ODE.
4898 
4899  Only makes sense if called on a top-level assignment
4900  expression, and checks if it represents an ODE, i.e. if the
4901  LHS is a derivative.
4902  """
4903  if not self.is_top_level():
4904  return False
4905  return type(self._cml_assigns_to) == types.TupleType
4906 
4907  def is_assignment(self):
4908  """Return True iff this is a straightforward assignment expression.
4909 
4910  Only makes sense if called on a top-level assignment expression.
4911  Checks that this is *not* an ODE, but assigns to a single variable.
4912  """
4913  if not self.is_top_level():
4914  return False
4915  return isinstance(self._cml_assigns_to, cellml_variable)
4916 
4918  """Return the variable assigned to by this assignment.
4919 
4920  Should only be called on a top-level assignment expression.
4921 
4922  If it's a straightforward assignment (so self.is_assignment()
4923  returns True) then return the cellml_variable object
4924  representing the variable assigned to.
4925 
4926  If it's an ODE, return a pair
4927  (dependent variable, independent variable).
4928  """
4929  if not self.is_top_level():
4930  raise TypeError("not a top-level apply element")
4931  else:
4932  return self._cml_assigns_to
4933 
4934  def _get_binding_time(self, check_operator=True):
4935  """Return the binding time of this expression.
4936 
4937  The binding time will be computed recursively and cached.
4938  It will also be made available as an attribute in the XML.
4939 
4940  It is computed by taking the least upper bound of the binding
4941  times of our operands, unless the operator possesses an
4942  alternative method.
4943  """
4944  if self._cml_binding_time is not None:
4945  return self._cml_binding_time
4946 
4947  # Do we have an annotation?
4948  if hasattr(self, u'binding_time'):
4949  self._cml_binding_time = getattr(BINDING_TIMES, self.binding_time)
4950  return self._cml_binding_time
4951 
4952  # Does operator have a specialised method for this?
4953  op = self.operator()
4954  if check_operator and hasattr(op, '_get_binding_time'):
4955  self._cml_binding_time = op._get_binding_time()
4956  else:
4957  # Compute operand binding times
4958  bts = [BINDING_TIMES.static]
4959  for operand in self.operands():
4960  bts.append(self._get_element_binding_time(operand))
4961  # Take l.u.b.
4962  self._cml_binding_time = max(bts)
4963 
4964  # Annotate the element with the binding time
4965  self.xml_set_attribute((u'pe:binding_time', NSS[u'pe']), unicode(self._cml_binding_time))
4966  return self._cml_binding_time
4967 
4968  def _reduce(self, check_operator=True):
4969  """Reduce this expression by evaluating its static parts."""
4970  # Check to see if this operator requires a special
4971  # reduction strategy
4972  op = self.operator()
4973  DEBUG('partial-evaluator', "Reducing", op.localName, getattr(self, u'id', ''))
4974  if check_operator and hasattr(op, '_reduce'):
4975  op._reduce()
4976  else:
4977  bt = self._get_binding_time()
4978  if bt == BINDING_TIMES.static:
4979  # Evaluate self and replace by a <cn>, <true> or <false>
4980  new_elt = self._eval_self()
4981  self._xfer_complexity(new_elt)
4982  self.replace_child(self, new_elt, self.xml_parent)
4983  # Update usage counts
4984  self._update_usage_counts(self, remove=True)
4985  elif bt == BINDING_TIMES.dynamic:
4986  # Recurse into operands and reduce those
4987  for op in self.operands():
4988  self._reduce_elt(op)
4989  return
4990 
4991  @staticmethod
4992  def create_new(elt, operator, operands=[], qualifiers=[]):
4993  """Create a new MathML apply element, with given content.
4994 
4995  elt should be any element in the document.
4996 
4997  operator is used as the name of the first, empty, child.
4998 
4999  operands is a list, possibly empty, of operand elements. If
5000  any member is a unicode object, it is considered to be the
5001  name of a variable. If a tuple, then it should be a pair of
5002  unicode objects: (number, units). (Although units can be an
5003  attribute dictionary.)
5004 
5005  qualifiers specifies a list of qualifier elements.
5006  """
5007  app = elt.xml_create_element(u'apply', NSS[u'm'])
5008  app.xml_append(app.xml_create_element(unicode(operator), NSS[u'm']))
5009  for qual in qualifiers:
5010  check_append_safety(qual)
5011  app.xml_append(qual)
5012  for op in operands:
5013  if isinstance(op, basestring):
5014  # Variable name
5015  op = app.xml_create_element(u'ci', NSS[u'm'],
5016  content=unicode(op))
5017  elif isinstance(op, tuple):
5018  # Constant with units
5019  if isinstance(op[1], dict):
5020  attrs = op[1]
5021  else:
5022  attrs = {(u'cml:units', NSS[u'cml']): unicode(op[1])}
5023  op = app.xml_create_element(u'cn', NSS[u'm'],
5024  attributes=attrs,
5025  content=unicode(op[0]))
5026  else:
5027  # Should already be an element
5029  app.xml_append(op)
5030  return app
5031 
5033  def __init__(self):
5034  super(mathml_piecewise, self).__init__()
5035  self._cml_units = None
5036  self._cml_binding_time = None
5037 
5038  def tree_complexity(self, **kw):
5039  """
5040  Calculate a rough estimate of the computation time for
5041  evaluating this <piecewise> element.
5042 
5043  The real evaluation time will generally depend on run time
5044  data, which makes things tricky. Here we estimate by taking
5045  the sum of the complexities of the conditions and the maximum
5046  of the complexity of the cases, in order to obtain an upper
5047  bound.
5048 
5049  If lookup_tables is True, then assume we're using lookup tables
5050  where possible.
5051  If algebraic is True, the complexity is calculated as a dictionary,
5052  mapping node types to the number of occurences of that type.
5053 
5054  If self.rootNode.num_lookup_tables exists, this method will
5055  update the count of lookup tables based on this expression,
5056  unless the argument 'count_tables' is False or algebraic is True.
5057  """
5058  kw['algebraic'] = kw.get('algebraic', False)
5059  alg = kw['algebraic']
5060  if alg: ac, piece_dicts = {}, []
5061  else: ac = 0
5062  piece_acs = []
5063  count_lts = hasattr(self.rootNode, 'num_lookup_tables') and kw.get('count_tables', True) and not alg
5064  if count_lts:
5065  # Alternative method of counting number of lookup tables;
5066  # handles the Zhang model better!
5067  num_lts = self.rootNode.num_lookup_tables
5068  piece_num_lts = []
5069 
5070  for piece in getattr(self, u'piece', []):
5071  test_ac = self._tree_complexity(child_i(piece, 2), **kw)
5072  if alg: add_dicts(ac, test_ac)
5073  else: ac += test_ac
5074  if count_lts:
5075  nlts = self.rootNode.num_lookup_tables
5076  piece_ac = self._tree_complexity(child_i(piece, 1), **kw)
5077  if alg:
5078  piece_dicts.append(piece_ac)
5079  piece_acs.append(self._tree_complexity(child_i(piece, 1), count_tables=False))
5080  else:
5081  piece_acs.append(piece_ac)
5082  if count_lts:
5083  piece_num_lts.append(self.rootNode.num_lookup_tables - nlts)
5084  if hasattr(self, u'otherwise'):
5085  if count_lts:
5086  nlts = self.rootNode.num_lookup_tables
5087  ow_ac = self._tree_complexity(child_i(self.otherwise, 1), **kw)
5088  if alg:
5089  piece_dicts.append(ow_ac)
5090  piece_acs.append(
5091  self._tree_complexity(child_i(self.otherwise, 1), count_tables=False))
5092  else:
5093  piece_acs.append(ow_ac)
5094  if count_lts:
5095  piece_num_lts.append(self.rootNode.num_lookup_tables - nlts)
5096  max_idx, max_piece_ac = max_i(piece_acs)
5097  if alg:
5098  add_dicts(ac, piece_dicts[max_idx])
5099  else:
5100  ac += max_piece_ac
5101  if count_lts:
5102  self.rootNode.num_lookup_tables -= sum(piece_num_lts)
5103  self.rootNode.num_lookup_tables += piece_num_lts[max_idx]
5104  return ac
5105 
5106  def _set_in_units(self, units, no_act=False):
5107  """Set the units this expression should be given in.
5108 
5109  This is done recursively by setting the units for each option.
5110 
5111  We also set the units on each condition to be boolean, since
5112  subexpressions of the conditions may need units conversions added.
5113  """
5114  # First, record our units
5115  if not no_act:
5116  self._cml_units = units
5117  # Now process our children
5118  boolean = self.model.get_units_by_name(u'cellml:boolean')
5119  for piece in getattr(self, u'piece', []):
5120  self._set_element_in_units(child_i(piece, 1), units, no_act)
5121  self._set_element_in_units(child_i(piece, 2), boolean, no_act)
5122  if hasattr(self, u'otherwise'):
5123  self._set_element_in_units(child_i(self.otherwise, 1), units, no_act)
5124 
5125  def get_units(self):
5126  """Recursively check this expression for dimensional consistency.
5127 
5128  The first child elements of each <piece> and <otherwise> element
5129  should have dimensionally equivalent units (the resulting <units>
5130  element will be dimensionally equivalent to these). The second child
5131  elements of each <piece> should have units of cellml:boolean.
5132 
5133  If consistent, returns a <units> element for the whole expression.
5134  Throws a UnitsError if the units are inconsistent.
5135  """
5136  if self._cml_units:
5137  return self._cml_units
5138  # Check the second child of each <piece> element
5139  boolean = self.model.get_units_by_name(u'cellml:boolean')
5140  for piece in getattr(self, u'piece', []):
5141  cond_elt = child_i(piece, 2)
5142  units = self._get_element_units(cond_elt)
5143  if not boolean in units:
5144  raise UnitsError(self, u' '.join([
5145  u'The second child element of a <piece> element must have'
5146  u'units of cellml:boolean, not',units.description()]))
5147  # Compare the first child element of each <piece> and the <otherwise>,
5148  # if present.
5149  our_units = None
5150  if hasattr(self, u'otherwise'):
5151  value_elt = child_i(self.otherwise, 1)
5152  our_units = self._get_element_units(value_elt).copy()
5153  for piece in getattr(self, u'piece', []):
5154  value_elt = child_i(piece, 1)
5155  if our_units is None:
5156  our_units = self._get_element_units(value_elt).copy()
5157  else:
5158  units = self._get_element_units(value_elt)
5159  if not our_units.dimensionally_equivalent(units):
5160  raise UnitsError(self, u' '.join([
5161  u'The first child elements of children of a piecewise',
5162  u'element must have dimensionally equivalent units;',
5163  units.description(),'and',our_units.description(),
5164  u'differ']))
5165  our_units.update(units)
5166  # Check that we have some units for this element
5167  if our_units is None:
5168  raise UnitsError(self, u' '.join([
5169  u'A piecewise element must have at least one piece or',
5170  u'otherwise child in order to have defined units.']))
5171  # Cache & return units
5172  self._cml_units = our_units
5173  our_units.set_expression(self)
5174  return self._cml_units
5175 
5176  def classify_variables(self, dependencies_only=False,
5177  needs_special_treatment=lambda n: None):
5178  """Classify variables in this expression according to how they are used.
5179 
5180  In the process, compute and return a set of variables on which
5181  this expression depends.
5182 
5183  If dependencies_only then the variable classification will not be
5184  done, only dependencies will be analysed. This is useful for doing
5185  a 'light' re-analysis if the dependency set has been reduced; if the
5186  set has increased then the topological sort of equations may need to
5187  be redone.
5188 
5189  The function needs_special_treatment may be supplied to override the
5190  default recursion into sub-trees. It takes a single sub-tree as
5191  argument, and should either return the dependency set for that
5192  sub-tree, or None to use the default recursion. This is used when
5193  re-analysing dependencies after applying lookup tables, since table
5194  lookups only depend on the keying variable.
5195  """
5196  dependencies = set()
5197  pieces = list(getattr(self, u'piece', []))
5198  if hasattr(self, u'otherwise'):
5199  pieces.append(self.otherwise)
5200  for piece in pieces:
5201  dependencies.update(self.classify_child_variables(piece, dependencies_only=dependencies_only,
5202  needs_special_treatment=needs_special_treatment))
5203  return dependencies
5204 
5205  def evaluate(self):
5206  """Evaluate this piecewise expression.
5207 
5208  Tests choices in the order they occur in the file.
5209  Only evaluates a choice if its condition evaluates to True.
5210  """
5211  for piece in getattr(self, u'piece', []):
5212  condition = child_i(piece, 2)
5213  cond_value = self.eval(condition)
5214  if cond_value is True:
5215  # This is the option to take
5216  value = self.eval(child_i(piece, 1))
5217  break
5218  else:
5219  # Evaluate the <otherwise>
5220  if hasattr(self, u'otherwise'):
5221  value = self.eval(_child1(self.otherwise))
5222  else:
5223  raise EvaluationError(u' '.join([
5224  "A piecewise element where the pieces aren't mutually",
5225  "exhaustive requires an otherwise element."]))
5226  return value
5227 
5229  """Return the binding time of this expression.
5230 
5231  The binding time will be computed recursively and cached.
5232  It will also be made available as an attribute in the XML.
5233 
5234  It is computed by taking the least upper bound of the binding
5235  times of (some of) the conditions and cases.
5236 
5237  Condition & case binding times are computed in the order given
5238  in the file. If a condition is static with value False, its
5239  associated case is not considered. If a condition is static
5240  with value True, subsequent conditions & cases and the
5241  otherwise (if present) will not be considered.
5242  """
5243  if self._cml_binding_time is not None:
5244  return self._cml_binding_time
5245 
5246  # Do we have an annotation?
5247  if hasattr(self, u'binding_time'):
5248  self._cml_binding_time = getattr(BINDING_TIMES,
5249  self.binding_time)
5250  return self._cml_binding_time
5251 
5252  # Compute condition binding times
5253  bts = [BINDING_TIMES.static]
5254  for piece in getattr(self, u'piece', []):
5255  condition = child_i(piece, 2)
5256  bt = self._get_element_binding_time(condition)
5257  if bt is BINDING_TIMES.static:
5258  cond_value = self.eval(condition)
5259  if cond_value is True:
5260  # Compute BT for associated case
5261  bts.append(self._get_element_binding_time(
5262  child_i(piece, 1)))
5263  # Skip remaining conditions & otherwise
5264  break
5265  else:
5266  # Don't need to append extra statics, since bts
5267  # contains at least one member that is static
5268  bts.append(bt)
5269  # Compute BT for associated case
5270  bts.append(self._get_element_binding_time(
5271  child_i(piece, 1)))
5272  else:
5273  # Consider the <otherwise> element
5274  if hasattr(self, u'otherwise'):
5275  bts.append(self._get_element_binding_time(
5276  child_i(self.otherwise, 1)))
5277  # Take least upper bound of appropriate binding times
5278  self._cml_binding_time = max(bts)
5279 
5280  # Annotate the element with the binding time
5281  self.xml_set_attribute((u'pe:binding_time', NSS[u'pe']),
5282  unicode(self._cml_binding_time))
5283  return self._cml_binding_time
5284 
5285  def _reduce(self, check_operator=True):
5286  """Reduce this expression by evaluating its static parts.
5287 
5288  Even in a dynamic conditional, where a condition is static and
5289  evaluates to False, the associated case is discarded.
5290  """
5291  # Element to replace this <piecewise> with, if any
5292  new_elt = None
5293  if self._get_binding_time() == BINDING_TIMES.static:
5294  # Evaluate self and replace by a <cn>, <true> or <false>
5295  new_elt = self._eval_self()
5296  elif self._get_binding_time() == BINDING_TIMES.dynamic:
5297  # Go through pieces and reduce where appropriate
5298  deletable_pieces = []
5299  found_dynamic_piece = False
5300  for piece in getattr(self, u'piece', []):
5301  condition = child_i(piece, 2)
5302  bt = self._get_element_binding_time(condition)
5303  if bt is BINDING_TIMES.static:
5304  cond_value = self.eval(condition)
5305  if cond_value is True:
5306  if not found_dynamic_piece:
5307  # Replace the entire piecewise element by our case
5308  # We don't replace if a previous piece had a
5309  # dynamic condition, since this would change
5310  # execution order, which could alter the semantics.
5311  new_elt = child_i(piece, 1)
5312  break
5313  else:
5314  # Discard this condition & case
5315  deletable_pieces.append(piece)
5316  elif bt is BINDING_TIMES.dynamic:
5317  found_dynamic_piece = True
5318  # Reduce the condition & case
5319  self._reduce_elt(condition)
5320  self._reduce_elt(child_i(piece, 1))
5321  else:
5322  # Didn't replace entire conditional
5323  # Remove pieces with False conditions
5324  for piece in deletable_pieces:
5325  self._update_usage_counts(piece, remove=True)
5326  self.xml_remove_child(piece)
5327  # Consider the <otherwise> element
5328  if hasattr(self, u'otherwise'):
5329  if not found_dynamic_piece:
5330  # All the <piece> elements were removed, so replace
5331  # the entire conditional by this <otherwise>
5332  new_elt = child_i(self.otherwise, 1)
5333  else:
5334  # Just reduce the <otherwise>
5335  self._reduce_elt(child_i(self.otherwise, 1))
5336  # Replace this element, if required
5337  if new_elt is not None:
5338  # Update usage counts for removed expressions
5339  for piece in getattr(self, u'piece', []):
5340  if not new_elt is child_i(piece, 1):
5341  self._update_usage_counts(piece, remove=True)
5342  else:
5343  # Condition is being removed
5344  self._update_usage_counts(child_i(piece, 2), remove=True)
5345  piece.xml_remove_child(child_i(piece, 2))
5346  piece.xml_remove_child(new_elt)
5347  if hasattr(self, u'otherwise') and \
5348  not new_elt is child_i(self.otherwise, 1):
5349  self._update_usage_counts(child_i(self.otherwise, 1),
5350  remove=True)
5351  # Do the replace
5352  self._xfer_complexity(new_elt)
5353  self.replace_child(self, new_elt, self.xml_parent)
5354  # May need to reduce our replacement
5355  self._reduce_elt(new_elt)
5356 
5357  @staticmethod
5358  def create_new(elt, pieces, otherwise=None):
5359  """Create a new piecewise element.
5360 
5361  elt is any element in the current document.
5362 
5363  pieces is a list of pairs of expressions: (case, condition).
5364 
5365  otherwise, if given, is the default case.
5366  """
5367  piecewise = elt.xml_create_element(u'piecewise', NSS[u'm'])
5368  for piece in pieces:
5369  case, cond = piece
5370  check_append_safety(case)
5371  check_append_safety(cond)
5372  piece_elt = elt.xml_create_element(u'piece', NSS[u'm'])
5373  piece_elt.xml_append(case)
5374  piece_elt.xml_append(cond)
5375  piecewise.xml_append(piece_elt)
5376  if otherwise:
5377  check_append_safety(otherwise)
5378  otherwise_elt = elt.xml_create_element(u'otherwise', NSS[u'm'])
5379  otherwise_elt.xml_append(otherwise)
5380  piecewise.xml_append(otherwise_elt)
5381  return piecewise
5382 
5383 
5385  """Class representing the MathML lambda construct.
5386 
5387  Note that we don't support lambda occuring in CellML models. However, it is used
5388  for defining special units conversion rules using the protocol syntax.
5389  """
5390  @staticmethod
5391  def create_new(elt, bound_var_names, body_expr):
5392  """Create a new lambda from the sequence of bvar names and expression."""
5393  lambda_ = elt.xml_create_element(u'lambda', NSS[u'm'])
5394  for bvar_name in bound_var_names:
5395  bvar = elt.xml_create_element(u'bvar', NSS[u'm'])
5396  bvar.xml_append(mathml_ci.create_new(elt, bvar_name))
5397  lambda_.xml_append(bvar)
5398  check_append_safety(body_expr)
5399  lambda_.xml_append(body_expr)
5400  return lambda_
5401 
5402 
5404  """Base class for MathML operator elements."""
5405  def wrong_number_of_operands(self, found, wanted):
5406  """Raise an EvaluationError due to wrong operand count.
5407 
5408  found is the number of operands found; wanted is a list of suitable
5409  numbers of operands.
5410  """
5411  raise EvaluationError(u''.join([
5412  "Wrong number of operands for <", self.localName, "> ",
5413  "(found ", str(found), "; wanted", ' or '.join(map(str, wanted)), ")"]))
5414 
5415 
5417  """Class representing the diff element, containing some useful methods."""
5418  @property
5420  """
5421  Return the variable object w.r.t which we are differentiating.
5422  """
5423  # Note that after units checking the <bvar> qualifier can be
5424  # assumed to exist.
5425  apply_elt = self.xml_parent
5426  if not hasattr(apply_elt.bvar, u'ci'):
5427  raise MathsError(apply_elt, u'Differential operator does not have a variable element as bound variable.')
5428  return apply_elt.bvar.ci.variable
5429 
5430  @property
5432  """
5433  Return the variable object being differentiated.
5434  """
5435  apply_elt = self.xml_parent
5436  operand = apply_elt.operands().next()
5437  if not operand.localName == u'ci':
5438  raise MathsError(apply_elt, u'Derivatives of non-variables are not supported.')
5439  return operand.variable
5440 
5441  def _set_var_types(self):
5442  """
5443  Set the types of the dependent & independent variables: State for
5444  the dependent variable and Free for the independent variable.
5445  Gives a validation warning if they already have 'incompatible'
5446  types.
5447  """
5448  dep, indep = self.dependent_variable, self.independent_variable
5449  model = self.xml_parent.model
5450 
5451  # The dependent variable should have an initial value
5452  if not dep.get_type() == VarTypes.Mapped and \
5453  not hasattr(dep, u'initial_value'):
5454  model.validation_warning(u' '.join([
5455  u'The state variable',dep.fullname(),
5456  u'does not have an initial value given.']),
5457  level=logging.WARNING_TRANSLATE_ERROR)
5458  # It doesn't make sense to compute a state variable
5459  if dep.get_type(follow_maps=True) == VarTypes.Computed:
5460  model.validation_warning(u' '.join([
5461  u'The state variable',dep.fullname(),
5462  u'is also assigned to directly.']),
5463  level=logging.WARNING_TRANSLATE_ERROR)
5464  dep._set_type(VarTypes.State)
5465 
5466  t = indep.get_type(follow_maps=True)
5467  if t != VarTypes.Free:
5468  if t != VarTypes.Unknown:
5469  if t == VarTypes.Computed:
5470  reason = u'is computed in an expression.'
5471  elif t == VarTypes.State:
5472  reason = u'is a state variable itself.'
5473  else:
5474  reason = u'has an initial value specified.'
5475  model.validation_warning(u' '.join([
5476  u'The derivative of',dep.fullname(),
5477  u'is taken with respect to',indep.fullname(),
5478  u'but the latter', reason]),
5479  level=logging.WARNING_TRANSLATE_ERROR)
5480  # TODO: Add to list of independent vars?
5481  indep._set_type(VarTypes.Free)
5482 
5484  """Return the binding time of the enclosing <apply> element.
5485 
5486  This is the binding time of the expression defining this ODE.
5487  """
5488  expr = self.dependent_variable.get_ode_dependency(
5489  self.independent_variable)
5490  return expr._get_binding_time()
5491 
5492  def _reduce(self):
5493  """Reduce this expression by evaluating its static parts.
5494 
5495  If the whole expression is static, proceed as normal for an
5496  <apply>. Otherwise just rename the variable references. We
5497  can't instantiate the definition, because there will always be
5498  another user - external code.
5499 
5500  This operator is special cased because we need to alter its
5501  qualifier, but mathml_apply only considers operands. MathML
5502  data binding can be annoying at times!
5503  """
5504  app = self.xml_parent
5505  bt = app._get_binding_time()
5506  if bt == BINDING_TIMES.static:
5507  # Evaluate this expression as normal
5508  app._reduce(check_operator=False)
5509  else:
5510  # Just update names to be canonical.
5511  for ci in [app.ci, app.bvar.ci]:
5512  ci._set_variable_obj(ci.variable.get_source_variable(recurse=True))
5513  ci._rename()
5514 
5515  @staticmethod
5516  def create_new(elt, bvar, state_var, rhs):
5517  """Construct an ODE expression: d(state_var)/d(bvar) = rhs."""
5518  bvar_elt = elt.xml_create_element(u'bvar', NSS[u'm'])
5519  bvar_elt.xml_append(mathml_ci.create_new(elt, bvar))
5520  diff = mathml_apply.create_new(elt, u'diff', [state_var], [bvar_elt])
5521  ode = mathml_apply.create_new(elt, u'eq', [diff, rhs])
5522  return ode
5523 
5525  def _reduce(self):
5526  """Reduce this expression by evaluating its static parts.
5527 
5528  If the whole expression is static, proceed as normal for an
5529  <apply>. Otherwise check if we have more than one static
5530  operand. If we do, we can combine them in a new static
5531  expression and evaluate that as a whole.
5532  """
5533  app = self.xml_parent
5534  bt = app._get_binding_time()
5535  if bt == BINDING_TIMES.static or not self.model.get_option('partial_pe_commutative'):
5536  # Evaluate this expression as normal
5537  app._reduce(check_operator=False)
5538  else:
5539  # Get binding times of operands
5540  static_opers = filter(lambda e: app._get_element_binding_time(e) == BINDING_TIMES.static,
5541  app.operands())
5542  if len(static_opers) > 1:
5543  # Remove them from app
5544  for oper in static_opers:
5545  app.safe_remove_child(oper)
5546  # Create the new expression
5547  new_expr = mathml_apply.create_new(self, self.localName, static_opers)
5548  # Put it in the model and reduce it to evaluate it properly
5549  app.xml_append(new_expr)
5550  new_expr._reduce()
5551  # Now reduce all our operands as normal
5552  app._reduce(check_operator=False)
5553 
5555  """Class representing the MathML <plus> operator."""
5556  def __init__(self):
5557  super(mathml_plus, self).__init__()
5558  return
5559 
5560  def evaluate(self):
5561  """Evaluate by summing the operands of the enclosing <apply>."""
5562  app = self.xml_parent
5563  ops = app.operands()
5564  value = 0
5565  for operand in ops:
5566  value += self.eval(operand)
5567  return value
5568 
5570  """Class representing the MathML <minus> operator."""
5571  def evaluate(self):
5572  """Evaluate the enclosing <apply> element.
5573 
5574  Behaviour depends on the number of operands: we perform
5575  either a unary or binary minus.
5576  """
5577  app = self.xml_parent
5578  ops = list(app.operands())
5579  if len(ops) == 1:
5580  value = -self.eval(ops[0])
5581  elif len(ops) == 2:
5582  value = self.eval(ops[0]) - self.eval(ops[1])
5583  else:
5584  self.wrong_number_of_operands(len(ops), [1, 2])
5585  return value
5586 
5588  """Class representing the MathML <times> operator."""
5589  def evaluate(self):
5590  """
5591  Evaluate by taking the product of the operands of the enclosing <apply>.
5592  """
5593  app = self.xml_parent
5594  ops = app.operands()
5595  value = 1
5596  for operand in ops:
5597  value *= self.eval(operand)
5598  return value
5599 
5601  """Class representing the MathML <divide> operator."""
5602  def evaluate(self):
5603  """Evaluate by dividing the 2 operands of the enclosing <apply>."""
5604  app = self.xml_parent
5605  ops = list(app.operands())
5606  if len(ops) != 2:
5607  self.wrong_number_of_operands(len(ops), [2])
5608  numer = self.eval(ops[0])
5609  denom = self.eval(ops[1])
5610  return numer/denom
5611 
5612  def _reduce(self):
5613  """Reduce this expression by evaluating its static parts.
5614 
5615  If the whole expression is static, proceed as normal for an
5616  <apply>. If just the denominator is static, transform the
5617  expression into a multiplication.
5618  """
5619  app = self.xml_parent
5620  bt = app._get_binding_time()
5621  if bt == BINDING_TIMES.static:
5622  # Evaluate this expression as normal
5623  app._reduce(check_operator=False)
5624  else:
5625  # Check binding time of the denominator
5626  ops = list(app.operands())
5627  if len(ops) != 2:
5628  self.wrong_number_of_operands(len(ops), [2])
5629  bt = app._get_element_binding_time(ops[1])
5630  if bt == BINDING_TIMES.static:
5631  # Create inverse expression and evaluate it
5632  dummy = self.xml_create_element(u'dummy', NSS[u'm'])
5633  app.replace_child(ops[1], dummy)
5634  new_expr = mathml_apply.create_new(
5635  self, u'divide', [(u'1', u'dimensionless'),
5636  ops[1]])
5637  app.replace_child(dummy, new_expr)
5638  app._reduce_elt(new_expr)
5639  # Change this expression to a <times>
5640  times = self.xml_create_element(u'times', NSS[u'm'])
5641  app.replace_child(self, times)
5642  # And finally reduce it as normal
5643  app._reduce(check_operator=False)
5644  else:
5645  # Evaluate this expression as normal
5646  app._reduce(check_operator=False)
5647 
5649  """Class representing the MathML <exp> operator."""
5650  def evaluate(self):
5651  """Return e to the power of the single operand."""
5652  app = self.xml_parent
5653  ops = list(app.operands())
5654  if len(ops) != 1:
5655  self.wrong_number_of_operands(len(ops), [1])
5656  return math.exp(self.eval(ops[0]))
5657 
5659  """Class representing the MathML <ln> operator."""
5660  def evaluate(self):
5661  """Return the natural logarithm of the single operand."""
5662  app = self.xml_parent
5663  ops = list(app.operands())
5664  if len(ops) != 1:
5665  self.wrong_number_of_operands(len(ops), [1])
5666  return math.log(self.eval(ops[0]))
5667 
5669  """Class representing the MathML <log> operator."""
5670  def evaluate(self):
5671  """Return the logarithm of the single operand."""
5672  app = self.xml_parent
5673  ops = list(app.operands())
5674  if len(ops) != 1:
5675  self.wrong_number_of_operands(len(ops), [1])
5676  if hasattr(app, u'logbase'):
5677  base = self.eval(app.logbase)
5678  else:
5679  base = 10
5680  return math.log(self.eval(ops[0]), base)
5681 
5683  """Class representing the MathML <abs> operator."""
5684  def evaluate(self):
5685  """Return the absolute value of the single operand."""
5686  app = self.xml_parent
5687  ops = list(app.operands())
5688  if len(ops) != 1:
5689  self.wrong_number_of_operands(len(ops), [1])
5690  return abs(self.eval(ops[0]))
5691 
5693  """Class representing the MathML <power> operator."""
5694  def _set_in_units(self, units, no_act=False):
5695  """Set the units of the application of this operator.
5696 
5697  Set the exponent to have units of dimensionless, and the operand to
5698  have an arbitrary member of its possible units set.
5699 
5700  Where these mean the <apply> doesn't have the given units, wrap it
5701  in suitable units conversion mathematics.
5702  """
5703  app = self.xml_parent
5704  defn_units_set = app.get_units()
5705  defn_units = defn_units_set.extract()
5706  app._add_units_conversion(app, defn_units, units, no_act)
5707  # Record which member of the set we used
5708  if not no_act:
5709  app._cml_units = defn_units
5710  # Set exponent units
5711  dimensionless = app.model.get_units_by_name('dimensionless')
5712  ops = list(app.operands())
5713  self._set_element_in_units(ops[1], dimensionless, no_act)
5714  # Set operand units
5715  for src_units_set, src_units in defn_units_set._get_sources(defn_units):
5716  expr = src_units_set.get_expression()
5717  self._set_element_in_units(expr, src_units, no_act)
5718  return
5719 
5720  def evaluate(self):
5721  """Return the first operand to the power of the second."""
5722  app = self.xml_parent
5723  ops = list(app.operands())
5724  if len(ops) != 2:
5725  self.wrong_number_of_operands(len(ops), [2])
5726  return self.eval(ops[0]) ** self.eval(ops[1])
5727 
5728  def _reduce(self):
5729  """Reduce this expression by evaluating its static parts.
5730 
5731  If the whole expression is static, proceed as normal for an <apply>.
5732  Otherwise check if the exponent is static and the expression being exponentiated
5733  is a <ci>. If so, and the exponent is equal to 2, 3, or 4, convert the expression
5734  to a multiplication.
5735  """
5736  app = self.xml_parent
5737  bt = app._get_binding_time()
5738  converted = False
5739  if bt != BINDING_TIMES.static and self.model.get_option('pe_convert_power'):
5740  base, expt = list(app.operands())
5741  expt_bt = app._get_element_binding_time(expt)
5742  if expt_bt == BINDING_TIMES.static and isinstance(base, mathml_ci):
5743  expt_val = self.eval(expt)
5744  if expt_val in [2,3,4]:
5745  # Do the conversion
5746  app.safe_remove_child(base)
5747  operands = [base]
5748  for _ in range(1, expt_val):
5749  operands.append(base.clone_self())
5750  base.variable._used()
5751  new_app = mathml_apply.create_new(app, u'times', operands)
5752  app.replace_child(app, new_app, app.xml_parent)
5753  # Finally, reduce the new expression, just to be safe!
5754  new_app._reduce()
5755  converted = True
5756  if not converted:
5757  # Evaluate this expression as normal
5758  app._reduce(check_operator=False)
5759 
5761  """Class representing the MathML <root> operator."""
5762  def _set_in_units(self, units, no_act=False):
5763  """Set the units of the application of this operator.
5764 
5765  Set the degree to have units of dimensionless, and the operand to
5766  have an arbitrary member of its possible units set.
5767 
5768  Where these mean the <apply> doesn't have the given units, wrap it
5769  in suitable units conversion mathematics.
5770  """
5771  app = self.xml_parent
5772  defn_units_set = app.get_units()
5773  defn_units = defn_units_set.extract()
5774  app._add_units_conversion(app, defn_units, units, no_act)
5775  # Record which member of the set we used
5776  if not no_act:
5777  app._cml_units = defn_units
5778  # Set degree units
5779  if hasattr(app, u'degree'):
5780  dimensionless = app.model.get_units_by_name('dimensionless')
5781  self._set_element_in_units(_child1(app.degree), dimensionless, no_act)
5782  # Set operand units
5783  for src_units_set, src_units in defn_units_set._get_sources(defn_units):
5784  expr = src_units_set.get_expression()
5785  self._set_element_in_units(expr, src_units, no_act)
5786 
5787  def evaluate(self):
5788  """
5789  Return the operand to the given degree, if present.
5790  Otherwise return the square root of the operand.
5791  """
5792  app = self.xml_parent
5793  ops = list(app.operands())
5794  if len(ops) != 1:
5795  self.wrong_number_of_operands(len(ops), [1])
5796  if hasattr(app, u'degree'):
5797  degree = self.eval(app.degree)
5798  else:
5799  degree = 2
5800  return self.eval(ops[0]) ** (1/degree)
5801 
5803  """Class representing the MathML <and> operator."""
5804  def evaluate(self):
5805  """Return the logical conjunction of the operands.
5806 
5807  Evaluates operands in the order given in the file, and will
5808  short-circuit at the first which evaluates to False.
5809  """
5810  app = self.xml_parent
5811  ops = app.operands()
5812  value = True
5813  for operand in ops:
5814  value = value and self.eval(operand)
5815  if not value: break
5816  return value
5817 
5819  """Return the binding time of the enclosing <apply> element.
5820 
5821  Short-circuit if a static False operand occurs before any dynamic
5822  operands, returning static. Otherwise return the least upper bound
5823  of operand binding times, as usual.
5824  """
5825  app = self.xml_parent
5826  bts = [BINDING_TIMES.static]
5827  for operand in app.operands():
5828  bt = app._get_element_binding_time(operand)
5829  if bt is BINDING_TIMES.static:
5830  value = self.eval(operand)
5831  if not value and len(bts) == 1:
5832  # Short-circuit
5833  break
5834  else:
5835  bts.append(bt)
5836  # Take least upper bound
5837  return max(bts)
5838 
5839  # TODO: Write a _reduce method
5840 
5842  """Class representing the MathML <or> operator."""
5843  def evaluate(self):
5844  """Return the logical disjunction of the operands.
5845 
5846  Evaluates operands in the order given in the file, and will
5847  short-circuit at the first which evaluates to True.
5848  """
5849  app = self.xml_parent
5850  ops = app.operands()
5851  value = False
5852  for operand in ops:
5853  value = value or self.eval(operand)
5854  if value: break
5855  return value
5856 
5858  """Return the binding time of the enclosing <apply> element.
5859 
5860  Short-circuit if a static True operand occurs before any dynamic
5861  operands, returning static. Otherwise return the least upper bound
5862  of operand binding times, as usual.
5863  """
5864  app = self.xml_parent
5865  bts = [BINDING_TIMES.static]
5866  for operand in app.operands():
5867  bt = app._get_element_binding_time(operand)
5868  if bt is BINDING_TIMES.static:
5869  value = self.eval(operand)
5870  if value and len(bts) == 1:
5871  # Short-circuit
5872  break
5873  else:
5874  bts.append(bt)
5875  # Take least upper bound
5876  return max(bts)
5877 
5878  # TODO: Write a _reduce method
5879 
5881  """Class representing the MathML <leq> operator."""
5882  def evaluate(self):
5883  """
5884  Return True iff the value of the first operand is
5885  less than or equal to the value of the second.
5886  """
5887  app = self.xml_parent
5888  ops = list(app.operands())
5889  if len(ops) != 2:
5890  self.wrong_number_of_operands(len(ops), [2])
5891  return self.eval(ops[0]) <= self.eval(ops[1])
5892 
5894  """Class representing the MathML <lt> operator."""
5895  def evaluate(self):
5896  """
5897  Return True iff the value of the first operand is
5898  less than the value of the second.
5899  """
5900  app = self.xml_parent
5901  ops = list(app.operands())
5902  if len(ops) != 2:
5903  self.wrong_number_of_operands(len(ops), [2])
5904  return self.eval(ops[0]) < self.eval(ops[1])
5905 
5907  """Class representing the MathML <geq> operator."""
5908  def evaluate(self):
5909  """
5910  Return True iff the value of the first operand is
5911  greater than or equal to the value of the second.
5912  """
5913  app = self.xml_parent
5914  ops = list(app.operands())
5915  if len(ops) != 2:
5916  self.wrong_number_of_operands(len(ops), [2])
5917  return self.eval(ops[0]) >= self.eval(ops[1])
5918 
5920  """Class representing the MathML <gt> operator."""
5921  def evaluate(self):
5922  """
5923  Return True iff the value of the first operand is
5924  greater than the value of the second.
5925  """
5926  app = self.xml_parent
5927  ops = list(app.operands())
5928  if len(ops) != 2:
5929  self.wrong_number_of_operands(len(ops), [2])
5930  return self.eval(ops[0]) > self.eval(ops[1])
5931 
5933  """Class representing the MathML <neq> operator."""
5934  def evaluate(self):
5935  """Evaluate the enclosing <apply> element.
5936 
5937  Return True iff the 2 operands are not equal.
5938  """
5939  app = self.xml_parent
5940  ops = list(app.operands())
5941  if len(ops) != 2:
5942  self.wrong_number_of_operands(len(ops), [2])
5943 
5944  return (self.eval(ops[0]) != self.eval(ops[1]))
5945 
5947  """Class representing the MathML <eq> operator."""
5948  def _is_top_level(self):
5949  """Return True iff the enclosing <apply> is a top-level expression."""
5950  return self.xml_parent.is_top_level()
5951 
5952  def _set_in_units(self, units, no_act=False):
5953  """Set the units of the application of this operator.
5954 
5955  If this is a top-level <eq/>, then force the RHS to take the units
5956  of the LHS. Otherwise, behave as for other relational operators.
5957  """
5958  if self._is_top_level():
5959  ops = self.xml_parent.operands()
5960  lhs = ops.next()
5961  lhs_units = lhs.get_units().extract()
5962  self._set_element_in_units(lhs, lhs_units, no_act)
5963  self._set_element_in_units(ops.next(), lhs_units, no_act)
5964  if not no_act:
5965  self.xml_parent._cml_units = units
5966  else:
5967  super(mathml_eq, self)._set_in_units(units, no_act)
5968 
5969  def evaluate(self):
5970  """Evaluate the enclosing <apply> element.
5971 
5972  The behaviour depends on whether the enclosing <apply> is a
5973  top-level expression or not, i.e. whether this is an
5974  assignment or a comparison.
5975 
5976  If an assignment, evaluate the RHS, assign the value to
5977  the variable on the LHS, and return it.
5978 
5979  If a comparison, return True iff the 2 operands are equal.
5980  """
5981  app = self.xml_parent
5982  ops = list(app.operands())
5983  if len(ops) != 2:
5984  self.wrong_number_of_operands(len(ops), [2])
5985 
5986  if self._is_top_level():
5987  # This is a top-level assignment or ODE
5988  value = self.eval(ops[1])
5989  var = app.assigned_variable()
5990  if app.is_assignment():
5991  var.set_value(value)
5992  elif app.is_ode():
5993  indepvar = var[1].get_source_variable(recurse=True)
5994  var[0].set_value(value, ode=indepvar)
5995  else:
5996  raise EvaluationError("Weird sort of assignment expression.")
5997  else:
5998  # This is a comparison
5999  value = (self.eval(ops[0]) == self.eval(ops[1]))
6000  return value
6001 
6003  """Return the binding time of the enclosing <apply> element.
6004 
6005  If this is a top-level expression, then only recurse into the RHS,
6006  otherwise proceed as normal for an apply.
6007 
6008  There is one further special case: if this is a top-level
6009  expression and the variable assigned to is annotated to be
6010  kept in the specialised model, then the expression is dynamic.
6011  """
6012  app = self.xml_parent
6013  if self._is_top_level():
6014  annotated_as_kept = False
6015  if app.is_ode():
6016  DEBUG('partial-evaluator', "BT ODE",
6017  map(lambda v: v.fullname(), app.assigned_variable()))
6018  else:
6019  DEBUG('partial-evaluator', "BT expr",
6020  app.assigned_variable().fullname())
6021  if app.assigned_variable().pe_keep:
6022  annotated_as_kept = True
6023  ops = list(app.operands())
6024  if len(ops) != 2:
6025  self.wrong_number_of_operands(len(ops), [2])
6026  rhs = ops[1]
6027  bt = app._get_element_binding_time(rhs)
6028  if annotated_as_kept:
6029  bt = BINDING_TIMES.dynamic
6030  else:
6031  bt = app._get_binding_time(check_operator=False)
6032  return bt
6033 
6034  def _reduce(self):
6035  """Reduce this expression by evaluating its static parts.
6036 
6037  If this is a top-level assignment, then just reduce the RHS.
6038  Otherwise proceed as normal for an <apply>.
6039  """
6040  app = self.xml_parent
6041  if self._is_top_level():
6042  ops = list(app.operands())
6043  if len(ops) != 2:
6044  self.wrong_number_of_operands(len(ops), [2])
6045  rhs = ops[1]
6046  app._reduce_elt(rhs)
6047  else:
6048  app._reduce(check_operator=False)
6049 
6050  @property
6051  def rhs(self):
6052  """Return the right hand side of this expression.
6053 
6054  Should only be called if we're actually an assignment.
6055  """
6056  if self._is_top_level():
6057  ops = self.xml_parent.operands()
6058  ops.next()
6059  return ops.next()
6060  else:
6061  raise ValueError("Not an assignment expression.")
6062  @property
6063  def lhs(self):
6064  """Return the left hand side of this expression.
6065 
6066  Should only be called if we're actually an assignment.
6067  """
6068  if self._is_top_level():
6069  ops = self.xml_parent.operands()
6070  return ops.next()
6071  else:
6072  raise ValueError("Not an assignment expression.")
6073 
6075  """Class representing the MathML <rem> operator."""
6076  def evaluate(self):
6077  """Return the remainder when the first operand is divided by the second."""
6078  app = self.xml_parent
6079  ops = list(app.operands())
6080  if len(ops) != 2:
6081  self.wrong_number_of_operands(len(ops), [2])
6082  return self.eval(ops[0]) % self.eval(ops[1])
6083 
6085  """Class representing the MathML <logbase> element."""
6086  def evaluate(self):
6087  """Evaluate this element, by evaluating its child."""
6088  return self.eval(_child1(self))
6089 
6091  """Class representing the MathML <degree> element."""
6092  def evaluate(self):
6093  """Evaluate this element, by evaluating its child."""
6094  return self.eval(_child1(self))
6095 
6097  """Class representing the MathML <otherwise> element.
6098 
6099  Only defined to make it inherit from mathml.
6100  """
6101  pass
6102 
6103 class mathml_piece(mathml):
6104  """Class representing the MathML <piece> element.
6105 
6106  Only defined to make it inherit from mathml.
6107  """
6108  pass
6109 
6111  """Class representing the MathML <sin> operator."""
6112  def evaluate(self):
6113  """Return the sine of the single operand."""
6114  app = self.xml_parent
6115  ops = list(app.operands())
6116  if len(ops) != 1:
6117  self.wrong_number_of_operands(len(ops), [1])
6118  return math.sin(self.eval(ops[0]))
6119 
6121  """Class representing the MathML <cos> operator."""
6122  def evaluate(self):
6123  """Return the cosine of the single operand."""
6124  app = self.xml_parent
6125  ops = list(app.operands())
6126  if len(ops) != 1:
6127  self.wrong_number_of_operands(len(ops), [1])
6128  return math.cos(self.eval(ops[0]))
6129 
6131  """Class representing the MathML <tan> operator."""
6132  def evaluate(self):
6133  """Return the tangent of the single operand."""
6134  app = self.xml_parent
6135  ops = list(app.operands())
6136  if len(ops) != 1:
6137  self.wrong_number_of_operands(len(ops), [1])
6138  return math.tan(self.eval(ops[0]))
6139 
6141  """Class representing the MathML <arcsin> operator."""
6142  def evaluate(self):
6143  """Return the arc sine of the single operand, in radians."""
6144  app = self.xml_parent
6145  ops = list(app.operands())
6146  if len(ops) != 1:
6147  self.wrong_number_of_operands(len(ops), [1])
6148  return math.asin(self.eval(ops[0]))
6149 
6151  """Class representing the MathML <arccos> operator."""
6152  def evaluate(self):
6153  """Return the arc cosine of the single operand, in radians."""
6154  app = self.xml_parent
6155  ops = list(app.operands())
6156  if len(ops) != 1:
6157  self.wrong_number_of_operands(len(ops), [1])
6158  return math.acos(self.eval(ops[0]))
6159 
6161  """Class representing the MathML <arctan> operator."""
6162  def evaluate(self):
6163  """Return the arc tangent of the single operand, in radians."""
6164  app = self.xml_parent
6165  ops = list(app.operands())
6166  if len(ops) != 1:
6167  self.wrong_number_of_operands(len(ops), [1])
6168  return math.atan(self.eval(ops[0]))
6169 
6170 
6171 ## Don't export module imports to people who do "from pycml import *"
6172 #__all__ = filter(lambda n: type(globals()[n]) is not types.ModuleType, globals().keys())
def import_processors
Definition: pycml.py:84
def get_multiplicative_factor
Definition: pycml.py:2876
def check_append_safety
Definition: pycml.py:197
def amara_parse_cellml
Definition: pycml.py:191
def _rel_error_ok
def eq(self, other): """Compare 2 units elements for equality.
Definition: pycml.py:2627
def dimensionally_equivalent
Definition: pycml.py:3185
def make_xml_binder
Helpful utility functions #.
Definition: pycml.py:168
def add_methods_to_amara
Definition: pycml.py:323
_cml_depends_on
Move the definition to this component defn._unset_cached_links() defn.xml_parent.xml_remove_child(def...
Definition: pycml.py:1668
def classify_child_variables
Definition: pycml.py:3965