Nektar++
pycml.py
Go to the documentation of this file.
2# We want 1/2==0.5
3from __future__ import division
4
5"""Copyright (c) 2005-2016, University of Oxford.
6All rights reserved.
7
8University of Oxford means the Chancellor, Masters and Scholars of the
9University of Oxford, having an administrative office at Wellington
10Square, Oxford OX1 2JD, UK.
11
12This file is part of Chaste.
13
14Redistribution and use in source and binary forms, with or without
15modification, 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
25THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
29LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
31GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
34OF 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
60import warnings
61warnings.simplefilter('ignore', DeprecationWarning)
62
63# Pythonic XML bindings
64import amara
65from amara import binderytools as bt
66
67from Ft.Xml import SplitQName
68from xml.dom import Node # For nodeType values
69
70import copy
71import itertools
72import math
73import operator
74import sys
75import types
76from cStringIO import StringIO
77
78from utilities import *
79from enum import Enum # Pythonic enums
80
81import cellml_metadata # Handle RDF metadata for CellML
82
83processors = 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######################################################################
97import logging
98
99# Default config for root logger
100logging.basicConfig(level=logging.CRITICAL,
101 format="%(name)s: %(levelname)s: %(message)s",
102 stream=sys.stderr)
103logging.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.
108logging.WARNING_TRANSLATE_ERROR = logging.WARNING + 5
109logging.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.
116NSS = {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
142VarTypes = Enum('Unknown', 'Free', 'State', 'MaybeConstant', 'Constant',
143 'Computed', 'Mapped')
144
145# Elements in the CellML subset of MathML
146CELLML_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
161BINDING_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
191def 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
207class 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
366class 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)
389 self._cml_units = {}
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
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:
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:
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:
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()):
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)
1107 """Remove the given assignment from our list.
1108
1109 This method is used by the partial evaluator."""
1110 self._cml_assignments.remove(a)
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
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
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
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)
1518 self._cml_units = None
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__()
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
1665 self._cml_value = {}
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
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
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
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
2349class 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
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
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
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
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
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
3337class EvaluationError(Exception):
3338 """
3339 Exception class for errors raised trying to evaluate a MathML expression.
3340 """
3341 pass
3342
3343class 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
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
3449def 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).")
3460def _child1(elt):
3461 "Get the first child element of elt."
3462 return child_i(elt, 1)
3463
3464
3465######################################################################
3466# MathML elements #
3467######################################################################
3468
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__()
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_cml_units:
4203 self._cml_units_cml_units = UnitsSet([self.component.get_units_by_name(self.units)], expression=self)
4204 if not return_set:
4205 u = self._cml_units_cml_units.extract()
4206 else:
4207 u = self._cml_units_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
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_cml_units:
4252 expression=self)
4253 if not return_set:
4254 u = self._cml_units_cml_units.extract()
4255 else:
4256 u = self._cml_units_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
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."""
4403 # Dependency graph edges
4406 self.clear_colour()
4407
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:
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
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
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
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(
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
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 __init__(self, context_obj, message, warn=False, level=None)
Definition: pycml.py:3347
def _generate_message(self, where)
Definition: pycml.py:3407
def __init__(self, context_obj, message, warn=False, level=None)
Definition: pycml.py:3434
def equals(self, other)
Definition: pycml.py:2413
def __new__(cls, iterable=[], expression=None)
Definition: pycml.py:2363
def set_expression(self, expr)
Definition: pycml.py:2441
def get_consistent_set(self, desired_units)
Definition: pycml.py:2387
def extract(self, check_equality=False)
Definition: pycml.py:2421
def __init__(self, iterable=[], expression=None)
Definition: pycml.py:2372
def _add_source(self, units, src_units_set, src_units)
Definition: pycml.py:2446
def _get_sources(self, units)
Definition: pycml.py:2474
def simplify(self, other_units=None, other_exponent=1)
Definition: pycml.py:2482
def dimensionally_equivalent(self, other_units)
Definition: pycml.py:2512
def _clear_hierarchy(self, reln_key)
Definition: pycml.py:1546
def _has_child_components(self, reln_key)
Definition: pycml.py:1572
def get_units_by_name(self, uname)
Definition: pycml.py:1594
def get_variable_by_name(self, varname)
Definition: pycml.py:1618
def parent(self, relationship=u 'encapsulation', namespace=None, name=None, reln_key=None)
Definition: pycml.py:1527
def _set_parent_component(self, reln_key, parent)
Definition: pycml.py:1551
def _add_child_component(self, reln_key, child)
Definition: pycml.py:1566
def _del_variable(self, var, keep_annotations=False)
Definition: pycml.py:1629
def add_units(self, name, units)
Definition: pycml.py:1604
def get_option(self, option_name)
Definition: pycml.py:409
def get_component_by_name(self, compname)
Definition: pycml.py:414
def _check_variable_units_exist(self)
Definition: pycml.py:774
def add_units(self, name, units)
Definition: pycml.py:1318
def get_variables_by_ontology_term(self, term)
Definition: pycml.py:459
def topological_sort(self, node)
Definition: pycml.py:1042
def has_units(self, units)
Definition: pycml.py:1327
def _report_exception(self, e, show_xml_context)
Definition: pycml.py:533
def build_component_hierarchy(self, relationship, namespace=None, name=None, rels=None)
Definition: pycml.py:958
def _del_component(self, comp)
Definition: pycml.py:507
def _add_sorted_assignment(self, a)
Definition: pycml.py:1095
def get_units_by_name(self, uname)
Definition: pycml.py:1309
def _check_connection_units(self, check_for_units_conversions=False)
Definition: pycml.py:786
def _check_unit_cycles(self, unit)
Definition: pycml.py:1181
def _is_new_units_obj(self, units)
Definition: pycml.py:1350
def _classify_variables(self, assignment_exprs, xml_context=False)
Definition: pycml.py:866
def _order_variables(self, assignment_exprs, xml_context=False)
Definition: pycml.py:893
def get_config(self, config_attr=None)
Definition: pycml.py:402
def search_for_assignments(self, comp=None)
Definition: pycml.py:918
def _add_component(self, comp, special=False)
Definition: pycml.py:499
def _validate_component_hierarchies(self)
Definition: pycml.py:632
def validate(self, xml_context=False, invalid_if_warnings=False, warn_on_units_errors=False, check_for_units_conversions=False, assume_valid=False, **ignored_kwargs)
Definition: pycml.py:549
def get_variable_by_cmeta_id(self, cmeta_id)
Definition: pycml.py:474
def build_name_dictionaries(self, rebuild=False)
Definition: pycml.py:932
def _validate_connection(self, conn)
Definition: pycml.py:689
def get_variable_by_name(self, compname, varname)
Definition: pycml.py:418
def _get_units_obj(self, units)
Definition: pycml.py:1337
def _add_units_obj(self, units)
Definition: pycml.py:1331
def _check_cellml_subset(self, math_elts, root=True)
Definition: pycml.py:853
def xml(self, stream=None, writer=None, **wargs)
Definition: pycml.py:1457
def _add_variable(self, var, varname, compname)
Definition: pycml.py:490
def validation_error(self, errmsg, level=logging.ERROR)
Definition: pycml.py:512
def validation_warning(self, errmsg, level=logging.WARNING)
Definition: pycml.py:522
def _check_maths_name_references(self, expr, xml_context=False)
Definition: pycml.py:812
def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[], state_vars_depend_on_odes=False, state_vars_examined=set())
Definition: pycml.py:1381
def _del_variable(self, varname, compname)
Definition: pycml.py:495
def _check_assigned_vars(self, assignments, xml_context=False)
Definition: pycml.py:842
def _check_dimensional_consistency(self, assignment_exprs, xml_context=False, warn_on_units_errors=False, check_for_units_conversions=False)
Definition: pycml.py:1154
def get_variable_by_oxmeta_name(self, name, throw=True)
Definition: pycml.py:439
def __eq__(self, other)
Definition: pycml.py:3247
def _set_units_element(self, obj, override=False)
Definition: pycml.py:3274
def __ne__(self, other)
Definition: pycml.py:3257
def __cmp__(self, other)
Definition: pycml.py:2607
def _set_type(self, var_type, _orig=None)
Definition: pycml.py:1894
def set_value(self, value, ode=None, follow_maps=True)
Definition: pycml.py:2147
def get_source_variable(self, recurse=False)
Definition: pycml.py:1870
def is_statically_const(self, ignore_annotations=False)
Definition: pycml.py:2119
def _set_source_variable(self, src_var)
Definition: pycml.py:1851
def remove_rdf_annotations(self, property=None)
Definition: pycml.py:2021
def fullname(self, cellml=False, debug=False)
Definition: pycml.py:1684
def set_is_modifiable_parameter(self, is_param)
Definition: pycml.py:2206
def _add_ode_dependency(self, independent_var, expr)
Definition: pycml.py:1796
def create_new(elt, name, units, id=None, initial_value=None, interfaces={})
Definition: pycml.py:2331
def get_value(self, ode=None)
Definition: pycml.py:2171
def get_variable_object(model, varname)
Definition: pycml.py:1730
def get_type(self, follow_maps=False)
Definition: pycml.py:1913
def set_is_derived_quantity(self, is_dq)
Definition: pycml.py:2219
def _reduce(self, update_usage=False)
Definition: pycml.py:2272
def _unset_binding_time(self, only_temporary=False)
Definition: pycml.py:2058
def get_ode_dependency(self, independent_var, context=None)
Definition: pycml.py:1827
def get_rdf_annotations(self, property)
Definition: pycml.py:2005
_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 get_rdf_annotation(self, property)
Definition: pycml.py:1988
def _set_binding_time(self, bt, temporary=False)
Definition: pycml.py:2042
def set_is_output_variable(self, is_ov)
Definition: pycml.py:2230
def set_rdf_annotation_from_boolean(self, property, is_yes)
Definition: pycml.py:2034
def _update_ode_dependency(self, free_var, defn)
Definition: pycml.py:1813
def _decrement_usage_count(self, follow_maps=True)
Definition: pycml.py:1947
def add_rdf_annotation(self, property, target, allow_dup=False)
Definition: pycml.py:1961
def __init__(self, body=None)
Definition: pycml.py:368
def xml_remove_child_at(self, index=-1)
Definition: pycml.py:248
def __setattr__(self, key, value)
Definition: pycml.py:225
def __delattr__(self, key)
Definition: pycml.py:216
def _get_binding_time(self, check_operator=True)
Definition: pycml.py:4934
def _is_qualifier(self, element)
Definition: pycml.py:4416
def _reduce(self, check_operator=True)
Definition: pycml.py:4968
def classify_variables(self, root=False, dependencies_only=False, needs_special_treatment=lambda n:None)
Definition: pycml.py:4786
def create_new(elt, operator, operands=[], qualifiers=[])
Definition: pycml.py:4992
def tree_complexity(self, **kw)
Definition: pycml.py:4458
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:4523
def get_units(self, return_set=True)
Definition: pycml.py:4248
def _rename(self, new_name=None)
Definition: pycml.py:4274
def _set_variable_obj(self, var)
Definition: pycml.py:4244
def classify_variables(self, dependencies_only=False, needs_special_treatment=lambda n:None)
Definition: pycml.py:4353
def _unset_cached_links(self, elt=None)
Definition: pycml.py:4226
def create_new(elt, variable_name)
Definition: pycml.py:4366
def create_new(elt, value, units)
Definition: pycml.py:4211
def get_units(self, return_set=True)
Definition: pycml.py:4200
def _update_usage_counts(self, expr, remove=False)
Definition: pycml.py:4123
def _get_element_binding_time(self, elt)
Definition: pycml.py:4057
def _get_element_units(self, elt, return_set=True)
Definition: pycml.py:4068
def _tree_complexity(self, elt, **kw)
Definition: pycml.py:4008
def create_new(elt, bvar, state_var, rhs)
Definition: pycml.py:5516
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:5952
def create_new(elt, bound_var_names, body_expr)
Definition: pycml.py:5391
def wrong_number_of_operands(self, found, wanted)
Definition: pycml.py:5405
def create_new(elt, pieces, otherwise=None)
Definition: pycml.py:5358
def classify_variables(self, dependencies_only=False, needs_special_treatment=lambda n:None)
Definition: pycml.py:5177
def _reduce(self, check_operator=True)
Definition: pycml.py:5285
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:5106
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:5694
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:5762
def _set_in_units(self, desired_units, no_act=False)
Definition: pycml.py:3619
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:3667
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:3589
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:3572
def _set_in_units(self, units, no_act=False)
Definition: pycml.py:3558
def _set_element_in_units(self, elt, units, no_act=False)
Definition: pycml.py:3534
def _add_units_conversion(self, expr, defn_units, to_units, no_act=False)
Definition: pycml.py:3471
def clone_self(self, register=False)
Definition: pycml.py:3730
def eval(self, elt)
Definition: pycml.py:3791
def _unset_cached_links(self, elt=None)
Definition: pycml.py:3767
def __deepcopy__(self, memo)
Definition: pycml.py:3694
def get_original_of_clone(self)
Definition: pycml.py:3740
def _ensure_units_exist(self, units=None, no_act=False)
Definition: pycml.py:3807
def Enum(*names)
Definition: enum.py:8
def _xfer_complexity(self, new_elt)
Definition: pycml.py:3911
def units
_u = units
Definition: pycml.py:3825
def get_units_by_name(self, uname)
Definition: pycml.py:2725
def amara_parse_cellml(source, uri=None, prefixes=None)
Definition: pycml.py:191
def simplify(self, other_units=None, other_exponent=1)
Definition: pycml.py:2967
def _best_parent(self, other_units)
Definition: pycml.py:3143
def create_new(parent, name, bases, add_to_parent=False, standard=False)
Definition: pycml.py:2912
def get_multiplier(self)
Definition: pycml.py:2888
def _adjust_complexity(self, old_elt, new_elt)
Definition: pycml.py:3923
def is_base_unit(self)
Definition: pycml.py:2857
def same_tree(self, other, this=None)
Definition: pycml.py:3897
def import_processors()
Definition: pycml.py:84
def check_append_safety(elt)
Definition: pycml.py:197
def model(self)
Definition: pycml.py:2654
def is_simple(self)
Definition: pycml.py:2860
def _based_on(self, units=None, prefix=None, exponent=None, multiplier=None, offset=None)
Definition: pycml.py:2941
def copy(self)
Definition: pycml.py:2663
def dimensionally_equivalent(self, other_units)
Definition: pycml.py:3185
def _child1(elt)
Definition: pycml.py:3460
def quotient(self, other_units)
Definition: pycml.py:3154
def description(self, force=False, cellml=False)
Definition: pycml.py:2670
def get_offset(self)
Definition: pycml.py:2899
def get_multiplicative_factor(self)
Definition: pycml.py:2876
def expand(self)
Definition: pycml.py:2730
def extract(self, check_equality=False)
Definition: pycml.py:2657
def make_xml_binder()
Helpful utility functions #.
Definition: pycml.py:168
def equals(self, other)
Definition: pycml.py:2634
def classify_child_variables(self, elt, **kwargs)
Definition: pycml.py:3965
def child_i(elt, i)
Definition: pycml.py:3449
def _rel_error_ok(self, value1, value2, tol)
def eq(self, other): """Compare 2 units elements for equality.
Definition: pycml.py:2627
def add_methods_to_amara()
Definition: pycml.py:323
def varobj(self, ci_elt)
Definition: pycml.py:3853
def vars_in(self, expr)
Definition: pycml.py:3870
def amara_parse(source, uri=None, rules=None, binderobj=None, prefixes=None)
Definition: utilities.py:192
def prid(obj, show_cls=False)
Definition: utilities.py:273
def DEBUG(facility, *args)
Definition: utilities.py:95
scalarT< T > abs(scalarT< T > in)
Definition: scalar.hpp:298
scalarT< T > log(scalarT< T > in)
Definition: scalar.hpp:303