3from __future__
import division
5"""Copyright (c) 2005-2016, University of Oxford.
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.
12This file is part of Chaste.
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.
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.
37# Initial work on a Python tool for processing CellML files.
39# - Featureful & draconian validation
61warnings.simplefilter(
'ignore', DeprecationWarning)
65from amara
import binderytools
as bt
67from Ft.Xml
import SplitQName
68from xml.dom
import Node
76from cStringIO
import StringIO
78from utilities
import *
87 if processors
is None:
91__version__ =
"$Revision: 25949 $"[11:-2]
100logging.basicConfig(level=logging.CRITICAL,
101 format=
"%(name)s: %(levelname)s: %(message)s",
103logging.getLogger().handlers[0].setLevel(logging.CRITICAL)
108logging.WARNING_TRANSLATE_ERROR = logging.WARNING + 5
109logging.addLevelName(logging.WARNING_TRANSLATE_ERROR,
'WARNING')
116NSS = {
u'm' :
u'http://www.w3.org/1998/Math/MathML',
117 u'cml':
u'http://www.cellml.org/cellml/1.0#',
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#',
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/",
137 u'doc' :
u"http://cellml.org/tmp-documentation"
142VarTypes =
Enum(
'Unknown',
'Free',
'State',
'MaybeConstant',
'Constant',
143 'Computed',
'Mapped')
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'])
161BINDING_TIMES =
Enum(
'static',
'dynamic')
170 Create a specialised binder, given some mappings from element names
171 to python classes,
and setting namespace prefixes.
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',
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)
192 """Parse a CellML source with default rules and bindings."""
194 rules = [bt.ws_strip_element_rule(
u'*')]
195 return amara_parse(source, rules=rules, binderobj=binder)
199 Check whether elt is safe to make a child, i.e. that it isn
't
200 already a child elsewhere.
202 assert getattr(elt,
'next_elem',
None)
is None
203 parent = getattr(elt,
'xml_parent',
None)
205 assert elt
not in parent.xml_children
209 Base element class to allow me to set certain attributes on my instances
210 that are Python objects rather than unicode strings.
214 super(element_base, self).
__init__()
218 Bypass Amara's __delattr__ for attribute names that start with _cml_
220 if key.startswith(
'_cml_'):
221 del self.__dict__[key]
223 amara.bindery.element_base.__delattr__(self, key)
227 Bypass Amara's __setattr__ for attribute names that start with _cml_
229 if key.startswith(
'_cml_'):
230 self.__dict__[key] = value
232 amara.bindery.element_base.__setattr__(self, key, value)
239 elif isinstance(self, mathml):
240 raise ValueError(
'MathML element with no parent!')
245 """Get the value of the cmeta:id attribute, or the empty string if not set."""
246 return self.getAttributeNS(NSS[
'cmeta'],
u'id')
250 Remove child object at a given index
251 index - optional, 0-based index of child to remove (defaults to the last child)
253 obj = self.xml_children[index]
254 if isinstance(obj, unicode):
255 del self.xml_children[index]
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)
265 del self.__dict__[attr]
266 if next: self.__dict__[attr] = next
268 prev, val = val, next
269 next = getattr(val,
'next_elem',
None)
271 prev.next_elem = next
273 del self.xml_children[index]
278 if hasattr(self,
'xml_attributes'):
279 msg.append(
'Object references based on XML attributes:')
283 source_phrase =
" based on '{%s}%s' in XML"%(ns, local)
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)))
296 count_phrase =
" (%s element)"%count
298 count_phrase =
" (%s elements)"%count
299 local, ns = val.localName, val.namespaceURI
301 source_phrase =
" based on '{%s}%s' in XML"%(ns, local)
303 source_phrase =
" based on '%s' in XML"%(local)
304 msg.append(attr+count_phrase+source_phrase)
305 return u'\n'.join(msg)
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__)
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]
324 def getAttributeNS(self, ns, local, default=u""):
326 Get the value of an attribute specified by namespace and localname.
328 Optionally can also
pass a default value
if the attribute
329 doesn
't exist (defaults to the empty string).
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)
339 def xml_element_children(self, elt=None):
340 """Return an iterable over child elements of this element."""
343 for child
in elt.xml_children:
344 if getattr(child,
'nodeType',
None) == Node.ELEMENT_NODE:
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
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)
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)
367 """An iterable version of comment nodes."""
369 amara.bindery.comment_base.__init__(self, body)
379 Specialised class for the
model element of a CellML document.
380 Adds methods
for collecting
and reporting validation errors, etc.
384 element_base.__init__(self)
399 """Try to get the RDF library to clean up nicely."""
400 cellml_metadata.remove_model(self)
403 """Get the configuration store if it exists, or an attribute thereof."""
404 config = getattr(self.xml_parent,
'_cml_config',
None)
406 config = getattr(config, config_attr,
None)
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)
415 """Return the component object that has name `compname'."""
420 Return the variable object with name `varname
' in component
427 if self.component.ignore_component_name:
428 compname = self.component.name
431 compname, varname = cellml_variable.split_name(varname)
441 Get the unique variable in this model
with the given Oxford metadata
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.
447 vars = cellml_metadata.find_variables(self,
448 ('bqbiol:is', NSS[
'bqbiol']),
449 (
'oxmeta:'+str(name), NSS[
'oxmeta']))
453 raise ValueError(
'"%s" does not name a unique variable (matches: %s)'
460 """Return a list of variables annotated with the given ontology term.
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.
466 The given term must be a (prefixed_name, nsuri) tuple.
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
476 Get the unique variable in this model
with the given cmeta:id attribute value.
478 vars = self.xml_xpath(u'cml:component/cml:variable[@cmeta:id="%s"]' % cmeta_id)
480 raise ValueError(
'"%s" does not ID a unique variable (matches: %s)'
481 % (cmeta_id, str(vars)))
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', []):
491 """Add a new variable to the model."""
496 """Remove a variable from the model."""
500 """Add a new component to the model."""
502 comp.xml_parent = self
504 self.xml_append(comp)
508 """Remove the given component from the model."""
509 self.xml_remove_child(comp)
513 """Log a validation error message.
515 Message should be a unicode string.
518 logging.getLogger('validator').
log(level, errmsg.encode(
'UTF-8'))
520 """Return the list of all errors found (so far) while validating this model."""
523 """Log a validation warning message.
525 Message should be a unicode string.
528 logging.getLogger('validator').
log(level, errmsg.encode(
'UTF-8'))
530 """Return the list of all warnings found (so far) while validating this model.
534 """Report an exception e as a validation error or warning.
536 If show_xml_context is True, display the XML of the context
537 of the exception
as well.
539 e.show_xml_context = show_xml_context
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.
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.
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.
561 If xml_context
is True, then the failing MathML tree will be
562 displayed
with every units error.
564 If check_for_units_conversions
is True, then generate a warning
if
565 units conversions will be needed.
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
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.
582 DEBUG(
'validator',
'Checked for units cycles')
595 for expr
in assignment_exprs:
619 warn_on_units_errors,
620 check_for_units_conversions)
623 unknown_nss = set(self.
rootNode.xml_namespaces.keys()).difference(set(NSS.values()))
626 u'\n '.join(list(unknown_nss)))
633 """Check Rule 6.4.3.2 (4): hierarchies must not be circular.
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.
641 for group
in getattr(self,
u'group', []):
643 for rel
in getattr(group,
u'relationship_ref', []):
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)
661 DEBUG(
'validator',
'Checked component hierachies')
664 """Check Rules 3.4.{5,6}: check variable mappings and interfaces are sane."""
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)
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':
681 var.get_source_variable()
685 "but no component exports a value to that variable."
686 % (var.fullname(), iface))
687 DEBUG(
'validator',
'Checked variable mappings')
690 """Validate the given connection element.
692 Check that the given connection object defines valid mappings
693 between variables, according to rules 3.4.5 and 3.4.6.
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)
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)
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)
713 par1, par2 = comp1.parent(), comp2.parent()
715 if not (par1 == comp2
or par2 == comp1
or par1 == par2):
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.']))
722 for mapping
in conn.map_variables:
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))
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))
737 errm, e = [
'Interface mismatch mapping',var1.fullname(),
'and',var2.fullname(),
':\n'],
None
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.'
749 if var1.public_interface ==
'in':
750 var1._set_source_variable(var2)
752 var2._set_source_variable(var1)
756 var1, var2 = var2, var1
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.'
765 if var1.public_interface ==
'in':
766 var1._set_source_variable(var2)
768 var2._set_source_variable(var1)
775 """Check rule 3.4.3.3: that the units declared for variables exist."""
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))
787 """Check that the units of mapped variables are dimensionally consistent.
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.
792 for conn
in getattr(self,
u'connection', []):
795 for mapping
in conn.map_variables:
799 u1 = var1.get_units()
800 u2 = var2.get_units()
802 if not u1.dimensionally_equivalent(u2):
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)
813 """Check rules 4.4.2.1 and 4.4.3.2: name references in mathematics."""
814 if isinstance(expr, mathml_ci):
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)),
826 elif isinstance(expr, mathml_cn):
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)),
839 for child
in expr.xml_element_children():
843 """Check Rule 4.4.4: mathematical expressions may only modify
844 variables belonging to the current component.
846 for expr
in assignments:
848 expr.check_assigned_var()
849 except MathsError, e:
851 DEBUG(
'validator',
'Checked variable assignments')
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']:
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.']))
864 DEBUG(
'validator',
'Checked for CellML subset')
867 """Determine the type of each variable.
869 Note that mapped vars must have already been classified by
871 that a variable cannot be both Mapped
and MaybeConstant.
873 Builds the equation dependency graph
in the process.
878 if hasattr(var,
u'initial_value'):
879 var._set_type(VarTypes.MaybeConstant)
882 for expr
in assignment_exprs:
884 expr.classify_variables(root=
True)
885 except MathsError, e:
889 if var.get_type() == VarTypes.MaybeConstant:
890 var._set_type(VarTypes.Constant)
891 DEBUG(
'validator',
'Classified variables')
894 """Topologically sort the equation dependency graph.
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).
904 if var.get_colour() == DFS.White:
906 for expr
in assignment_exprs:
907 if expr.get_colour() == DFS.White:
909 except MathsError, e:
911 DEBUG(
'validator',
'Topologically sorted variables')
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]'
919 """Search for assignment expressions in the model's mathematics.
921 If comp is supplied, will only
return assignments
in that component.
928 assignments_xpath = assignments_xpath.replace(
u'component',
929 u'component[@name="%s"]' % comp.name)
930 return self.xml_xpath(assignments_xpath)
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).
939 If rebuild
is True, clear the dictionaries first.
945 for comp
in getattr(self,
u'component', []):
947 self.
validation_error(
"Component names must be unique within a model (3.4.2.2)."
948 " The name '%s' is repeated." % comp.name)
950 for var
in getattr(comp,
u'variable', []):
951 key = (comp.name, var.name)
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))
960 Create all the parent-child links for the given component hierarchy.
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.
967 key = (relationship, namespace, name)
969 for comp
in getattr(self,
u'component', []):
970 comp._clear_hierarchy(key)
974 rels = self.xml_xpath(
u'cml:group/cml:relationship_ref')
980 if hasattr(rel,
u'relationship_'):
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())))
987 if rel.relationship == relationship
and \
988 rel.xml_attributes[
u'relationship'][1] == namespace
and \
989 getattr(rel,
u'name',
None) == name:
991 groups.append(rel.xml_parent)
993 def set_parent(p, crefs):
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)
1003 c._set_parent_component(key, p)
1004 if hasattr(cref,
'component_ref'):
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)
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)
1025 def has_cycle(root_comp, cur_comp):
1026 if cur_comp
is None:
1028 elif cur_comp
is root_comp:
1031 return has_cycle(root_comp, cur_comp.parent(reln_key=key))
1032 for group
in groups:
1033 for cref
in group.component_ref:
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))
1044 Do a topological sort of all assignment expressions and variables
1047 node should be an expression
or variable object that inherits
from
1048 Colourable
and has methods get_dependencies, get_component
1050 node.set_colour(DFS.Gray)
1052 if isinstance(node, cellml_variable):
1057 n1, n2 = map(
lambda v: v.fullname(), node.assigned_variable())
1060 for dep
in node.get_dependencies():
1061 if type(dep) == types.TupleType:
1063 dep = dep[0].get_ode_dependency(dep[1], node)
1064 if dep.get_colour() == DFS.White:
1066 elif dep.get_colour() == DFS.Gray:
1068 if isinstance(dep, cellml_variable):
1071 n1, n2 = map(
lambda v: v.fullname(),
1072 dep.assigned_variable())
1085 u'There is a cyclic dependency involving the following',
1086 u'variables:',
u','.join(varnames)]))
1088 node.set_colour(DFS.Black)
1091 if (isinstance(node, cellml_variable)
or node.is_ode()):
1097 During the topological sort, add a finished assignment to the
1098 list. This list can then be executed in order to simulate the
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.
1107 """Remove the given assignment from our list.
1109 This method is used by the partial evaluator.
"""
1113 Return a sorted list of all the assignments in the model.
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.
1121 """Clear the assignments list."""
1125 """Perform a binding time analysis on the model's mathematics.
1127 This requires variables to have been classified and a
1128 topological sort of the mathematics to have been performed.
1130 Variables
and top-level expressions are processed
in the order
1131 given by the topological sort, hence only a single
pass is
1134 Variables are classified based on their type:
1135 State, Free -> dynamic
1137 Mapped -> binding time of source variable
1138 Computed -> binding time of defining expression
1140 Expressions are dealt
with by recursively annotating
1141 subexpressions. See code
in the MathML classes
for details.
1144 if isinstance(item, cellml_variable):
1146 item._get_binding_time()
1149 item._get_binding_time()
1153 warn_on_units_errors=False,
1154 check_for_units_conversions=False):
1155 """Appendix C.3.6: Equation dimension checking."""
1158 for expr
in assignment_exprs:
1161 except UnitsError, e:
1162 if warn_on_units_errors:
1164 e.level = logging.WARNING
1169 for expr
in assignment_exprs:
1172 expr._set_in_units(boolean, no_act=
True)
1178 level=logging.WARNING)
1179 DEBUG(
'validator',
'Checked units')
1182 """Check for cyclic units definitions.
1184 We do this by doing a depth-first search from unit.
1186 if unit.get_colour() != DFS.White:
1189 unit.set_colour(DFS.Gray)
1191 parent = unit.xml_parent
or self
1192 for u
in getattr(unit,
u'unit', []):
1195 v = parent.get_units_by_name(u.units)
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)
1202 if v.get_colour() == DFS.White:
1204 elif v.get_colour() == DFS.Gray:
1207 % (unit.name, v.name))
1208 unit.set_colour(DFS.Black)
1211 """Create a dictionary mapping units names to objects, for all units definitions in this element."""
1214 def make(name, bases):
1215 return cellml_units.create_new(self, name, bases, standard=
True)
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#')
1220 for units
in base_units:
1221 std_units[units] = make(units, [])
1223 boolean = make(
u'cellml:boolean', [])
1224 std_units[
u'cellml:boolean'] = boolean
1226 gram = make(
'gram', [{
'units':
'kilogram',
'multiplier':
'0.001'}])
1227 litre = make(
'litre', [{
'multiplier':
'1000',
'prefix':
'centi',
1228 'units':
'metre',
'exponent':
'3'}])
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'},
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,
1276 std_units[units.name] = units
1278 std_units[
u'meter'] = std_units[
u'metre']
1279 std_units[
u'liter'] = std_units[
u'litre']
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
1291 for u
in model_units.itervalues():
1295 """Get a dictionary mapping the names of the standard CellML units to their definitions."""
1301 """Get a list of all units objects, including the standard units."""
1305 for comp
in getattr(self,
u'component', []):
1306 units.extend(comp.get_all_units())
1310 """Return an object representing the element that defines the units named `uname'."""
1316 raise KeyError(
"Units '%s' are not defined in the current component or model." % uname)
1319 """Add an entry in our units dictionary for units named `name' with element object `units'."""
1328 """Test whether a given units definition appears in the model."""
1332 """Add a units object into the global hashmap."""
1338 """Unique-ify this units object.
1340 If an object with the same definition already exists,
return that.
1341 Otherwise
return the given units object.
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.
1351 """Have these units been generated already?
1353 i.e. is a units object
with this definition
in our map?
1358 """Add explicit units conversion mathematics where necessary."""
1363 """Return a list of the state variable elements in this model."""
1366 if var.get_type() == VarTypes.State:
1367 state_vars.append(var)
1371 """Return a list of the free variable elements in this model."""
1374 if var.get_type() == VarTypes.Free:
1375 free_vars.append(var)
1380 state_vars_depend_on_odes=False,
1381 state_vars_examined=set()):
1382 """Calculate the extended dependencies of the given nodes.
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.
1388 Each node IS included
in its own dependency set.
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.
1397 If state_vars_depend_on_odes
is True, then considers state variables
1398 to depend on the ODE defining them.
1400 Requires the dependency graph to be acyclic.
1402 Return the union of all the dependency sets.
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):
1411 if type(node) == tuple:
1415 node = node[0].get_ode_dependency(node[1])
1416 if orig_node
in prune_deps:
1420 free_var = node.eq.lhs.diff.independent_variable
1424 if node
in prune_deps:
1427 nodedeps = set(node.get_dependencies())
1428 if ode
and not node._cml_ode_has_free_var_on_rhs:
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)
1441 prune_deps=prune_deps,
1442 state_vars_depend_on_odes=state_vars_depend_on_odes,
1443 state_vars_examined=state_vars_examined))
1447 """Determine whether this model is self-excitatory,
1448 i.e. does not require an external stimulus.
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'
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.
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.
1468 See base
class docs for possible keyword arguments.
1470 extra_namespaces = {u'cellml': NSS[
u'cml'],
1472 u'lut': NSS[
u'lut']}
1475 cellml_metadata.update_serialized_rdf(self)
1481 if not wargs.get(
'omitXmlDeclaration'):
1482 wargs[
'omitXmlDeclaration'] =
u'yes'
1484 writer = amara.bindery.create_writer(stream, wargs)
1486 temp_stream = StringIO()
1487 writer = amara.bindery.create_writer(temp_stream, wargs)
1489 writer.startDocument()
1491 writer.startElement(self.nodeName, self.namespaceURI,
1492 extraNss=extra_namespaces)
1493 if hasattr(self,
'xml_attributes'):
1496 val = self.__dict__[apyname]
1497 writer.attribute(aqname, val, ans)
1498 for child
in self.xml_children:
1499 if isinstance(child, unicode):
1502 child.xml(writer=writer)
1503 writer.endElement(self.nodeName, self.namespaceURI)
1505 writer.endDocument()
1506 return temp_stream
and temp_stream.getvalue()
1511 Specialised component class,
with additional helper methods.
1515 element_base.__init__(self)
1523 """Whether to not include the component name in the full names of contained variables."""
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.
1530 We default to the encapsulation hierarchy.
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
1538 Results are cached
for efficiency.
1540 key = reln_key or (relationship, namespace, name)
1542 assert(reln_key
is None)
1543 self.xml_parent.build_component_hierarchy(relationship, namespace, name)
1547 """Unset our parent & children in the given hierarchy."""
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.
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."
1563 if not parent
is None:
1564 parent._add_child_component(reln_key, self)
1567 """Add child to our list of children in the relationship hierarchy indexed by reln_key."""
1573 """Determine whether we have any children in the given relationship hierarchy."""
1578 """Create a dictionary mapping units names to objects, for all units definitions in this element."""
1580 for units
in getattr(self,
u'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))
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))
1593 self.xml_parent._add_units_obj(units)
1595 """Return an object representing the element that defines the units named `uname'."""
1605 """Add an entry in our units dictionary for units named `name' with element object `units'."""
1609 self.xml_parent._add_units_obj(units)
1613 """Get a list of all units objects defined in this component."""
1619 """Return the variable object with name `varname' in this component."""
1623 """Add a variable to this component."""
1625 self.xml_append(var)
1630 """Remove a variable from this component."""
1631 if not keep_annotations:
1633 var.remove_rdf_annotations()
1635 self.xml_remove_child(var)
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)})
1650 Class representing CellML <variable> elements.
1653 super(cellml_variable, self).
__init__()
1658 """Clear the type, dependency, etc. information for this variable.
1660 This allows us to re-run the type & dependency analysis for the model.
1674 """Hashing function for variables.
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!
1680 TODO: do we need a hash function?
1682 return hash(self.
fullname(cellml=
True))
1686 Return the full name of this variable, i.e.
1687 '(component_name,variable_name)'.
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.
1694 If debug
is True, also give information about the variable object.
1696 if hasattr(self,
'xml_parent'):
1697 parent_name = self.xml_parent.name
1698 ignore_component = self.
component.ignore_component_name
1700 parent_name =
'*orphan*'
1701 ignore_component =
True
1703 if ignore_component:
1706 vn = parent_name +
u'__' + self.name
1708 vn =
u'(' + parent_name +
u',' + self.name +
u')'
1710 vn =
'%s@0x%x' % (vn, id(self))
1715 """Split a variable name as given by cellml_variable.fullname into constituent parts.
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.
1721 if varname[0] ==
u'(':
1722 cname, vname = varname[1:-1].split(
u',')
1724 cname, vname = varname.split(
u'__', 1)
1726 cname, vname =
u'', varname
1731 """Return the variable object that has name varname.
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.
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)
1745 var = model.get_variable_by_name(cname, vname)
1749 vname = cname +
u'__' + vname
1750 var = model.component.get_variable_by_name(vname)
1756 return 'cellml_variable' + self.
fullname()
1759 return '<cellml_variable %s @ 0x%x>' % (self.
fullname(), id(self))
1762 return self.xml_parent
1763 component = property(get_component)
1770 """Get the cellml_units object giving this variable's units."""
1774 """Add a dependency of this variable.
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.
1785 u'gets its value from multiple locations.']))
1792 Return the list of things this variable depends on.
1797 """Add a dependency of this variable as the dependent variable in an ODE.
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.
1807 u'There are multiple definitions of the ODE d',self.
fullname(),
1808 u'/d',independent_var.fullname()]))
1814 """Update an ODE dependency due to partial evaluation.
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
1822 if self.
get_type() == VarTypes.Mapped:
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.
1833 if self.
get_type() == VarTypes.Mapped:
1836 free_vars = dict(zip(map(
lambda v: v.get_source_variable(recurse=
True), free_vars),
1838 independent_src = independent_var.get_source_variable(recurse=
True)
1839 if not independent_src
in free_vars:
1841 u'The ODE d',self.
fullname(),
u'/d',independent_var.fullname(),
1842 u'is used but not defined.']))
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)
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.
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',
1863 src_var.fullname(debug=debug),
'are exported to', self.
fullname(debug=debug)]))
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
1877 If recurse
is set to
True, recursively follow mappings until
1878 a non-mapped variable
is found.
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).'
1891 src = src.get_source_variable(recurse=
True)
1895 """Update the type of this variable.
1897 The caller should check that the update makes sense.
1899 If this variable already has type Mapped, then update the type of
1900 our source variable, instead.
1903 if var_type
is VarTypes.State
and not self.
_cml_var_type is VarTypes.State:
1905 if self.
_cml_var_type == VarTypes.Mapped
and not _orig
is self:
1907 if _orig
is None: _orig = self
1914 """Return the type of this variable.
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.
1925 """Note this variable as being used in an expression.
1927 Keep track of the usage count.
1928 If this is a mapped variable, note its source
as being used,
1931 Note that
if a variable
is used
in 2 branches of a conditional
1932 then this counts
as 2 uses.
1943 Return the number of times this variable is used
in an expression.
1948 """Decrement our usage count."""
1949 DEBUG(
'partial-evaluator',
"Dec usage for", self.
fullname())
1957 model = self.xml_parent.xml_parent
1958 model._pe_repeat =
u'yes'
1962 """Add an RDF annotation about this variable.
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.
1968 If the variable does
not already have a cmeta:id, one will be created
1969 for it
with value self.
fullname(cellml=
True).
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.
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)
1984 cellml_metadata.add_statement(self.
model, source, property, target)
1986 cellml_metadata.replace_statement(self.
model, source, property, target)
1989 """Get an RDF annotation about this variable.
1991 property must be a tuple (qname, namespace_uri).
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.
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)
2006 """Get all RDF annotations about this variable that use the given property.
2008 property must be a tuple (qname, namespace_uri).
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
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)
2022 """Remove all RDF annotations about this variable.
2024 If property is given, only remove annotations
with the given property.
2028 DEBUG(
'cellml-metadata',
"Removing RDF annotations for", self,
"with id", meta_id)
2029 source = cellml_metadata.create_rdf_node(fragment_id=meta_id)
2031 property = cellml_metadata.create_rdf_node(property)
2032 cellml_metadata.remove_statements(self.
model, source, property,
None)
2035 """Set an RDF annotation as 'yes' or 'no' depending on a boolean value."""
2043 """Set the binding time of this variable.
2045 Options are members of the BINDING_TIMES Enum.
2047 If temporary is True, then we
're temporarily overriding the normal setting,
2048 so save any existing annotation for later replacement.
2051 assert bt
in BINDING_TIMES
2052 if temporary
and not hasattr(self,
'_cml_saved_bt'):
2059 """Unset any stored binding time.
2061 If the stored BT was a temporary setting, replace it with the original value.
2065 if hasattr(self,
'_cml_saved_bt'):
2071 elif not only_temporary:
2075 """Return the binding time of this variable, as a member of
2076 the BINDING_TIMES Enum.
2078 This method will (try to) compute & cache the binding time
if
2081 Variables are classified based on their type:
2082 State, Free -> dynamic
2084 Mapped -> binding time of source variable
2085 Computed -> binding time of defining expression
2091 bt = getattr(BINDING_TIMES, bt_annotation)
2092 DEBUG(
'partial-evaluator',
"BT var", self.
fullname(),
"is annotated as", bt)
2095 bt = BINDING_TIMES.dynamic
2096 DEBUG(
'partial-evaluator',
"BT var", self.
fullname(),
"is kept")
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:
2110 raise TypeError(
"Unexpected variable type " + str(t) +
2111 " of variable " + self.
fullname() +
2113 DEBUG(
'partial-evaluator',
"BT var", self.
fullname(),
"is", bt)
2120 """Determine loosely if this variable is considered constant.
2122 Checks if we
're Constant, or Computed with a static binding time (or
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.
2131 if t
in [VarTypes.Constant, VarTypes.Unknown]:
2133 elif t == VarTypes.Computed:
2134 if ignore_annotations:
2135 dependencies = self.
model.calculate_extended_dependencies([self])
2137 for node
in dependencies:
2138 if isinstance(node, cellml_variable)
and node.get_type()
in [VarTypes.State, VarTypes.Free]:
2143 elif t == VarTypes.Mapped:
2148 """Set the value of this variable.
2150 Expects a floating point or boolean value.
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).
2155 If this is a mapped variable, assign the value to its source
2156 variable instead, unless follow_maps
is set to
False
2159 if follow_maps
and self.
get_type() == VarTypes.Mapped:
2162 assert type(value)
in [types.FloatType, types.BooleanType]
2166 """Unset all values for this variable set with set_value."""
2168 if self.
get_type() == VarTypes.Mapped:
2172 """Return the value of this variable.
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.
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).
2186 elif self.
get_type() == VarTypes.Mapped:
2188 elif ode
is None and self.
get_type()
in [VarTypes.Unknown,
2189 VarTypes.State, VarTypes.Constant]:
2190 if hasattr(self,
'initial_value'):
2203 """Whether this variable is a parameter that should be modifiable at run-time."""
2204 return (self.
get_type() == VarTypes.Constant
and
2207 """Set method for the is_modifiable_parameter property.
2209 We need a separate method for this to bypass Amara
's property setting checks.
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(),))
2217 """Whether this variable should be included in reports of derived quantities."""
2220 """Set method for the is_derived_quantity property.
2222 We need a separate method for this to bypass Amara
's property setting checks.
2228 """Whether a protocol has requested this variable as a model output."""
2231 """Set method for the is_output_variable property.
2233 We need a separate method for this to bypass Amara
's property setting checks.
2239 """Whether PE should retain this variable in the specialised model."""
2245 """Set method for the pe_keep property.
2247 We need a separate method for this to bypass Amara
's property setting checks.
2253 """The canonical name of this variable, as given by Oxford metadata.
2255 Returns the empty string if no annotation
is given.
2258 name = cellml_metadata.namespace_member(annotation, NSS[
'oxmeta'], wrong_ns_ok=
True)
2264 """Set method for the oxmeta_name property.
2266 Sets a bqbiol:is RDF annotation
with the name.
2268 We need a separate method
for this to bypass Amara
's property setting checks.
2270 self.add_rdf_annotation(('bqbiol:is', NSS[
'bqbiol']), (
'oxmeta:'+name, NSS[
'oxmeta']))
2273 """Reduce this dynamic variable that is being kept.
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.
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.
2285 if src._get_binding_time()
is BINDING_TIMES.static:
2289 value = unicode(src.get_value())
2297 elif self.
get_type() == VarTypes.Mapped:
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:
2314 defn = src.get_dependencies()[0]
2315 assert isinstance(defn, mathml_apply)
2321 defn._cml_assigns_to = self
2322 defn._pe_process =
u'retarget'
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.
2334 Optionally id, initial_value, and interfaces may also be given.
2336 elt may be any existing XML element.
2338 attrs = {(u'units',
None): unicode(units),
2339 (
u'name',
None): unicode(name)}
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)
2350 """A set of cellml_units objects.
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
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.
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.
2370 return super(UnitsSet, cls).
__new__(cls, iterable)
2373 super(UnitsSet, self).
__init__(iterable)
2379 """Do a shallow copy of this UnitsSet."""
2380 new_set = super(UnitsSet, self).
copy()
2382 new_set._sources = {}
2383 for units, src_list
in self.
_sources.iteritems():
2384 new_set._sources[units] = copy.copy(src_list)
2388 """Extract a subset of the units in this set that are dimensionally equivalent.
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.
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!).
2401 if units.dimensionally_equivalent(desired_units):
2403 new_set._sources[units] = copy.copy(self.
_sources[units])
2407 if not units.dimensionally_equivalent(rep_u):
2408 raise ValueError(
"Unexpected dimensional variation in UnitsSet; " + rep_u.description() +
" and " + units.description()
2414 """Test whether the units in the set are equal to those in another set."""
2416 equal = self.
extract(check_equality=
True).
equals(other.extract(check_equality=
True))
2422 """Extract a representative element from this set.
2424 This is intended to be used to get the cellml_units object
from a singleton set.
2426 If check_equality
is True, check that all members of this set have the same multiplicative factor.
2428 representative = iter(self).next()
2434 raise ValueError(
"UnitsSet equality check failed")
2435 if u.is_simple()
and not u._rel_error_ok(u.expand().
get_offset(),
2438 raise ValueError(
"UnitsSet equality check failed")
2439 return representative
2442 """Store a reference to the expression that has these units."""
2447 """Add source units for the given units.
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.
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.
2460 if not hasattr(src_units_set,
'_expression'):
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))
2467 self.
_sources[units].extend(src_units_set._sources[src_units])
2475 """Return the sources list for the given units."""
2476 return self.
_sources.get(units, [])
2479 """Return an expression that has these units."""
2482 def simplify(self, other_units=None, other_exponent=1):
2483 """Simplify the units in this set.
2485 Each cellml_units object in this set
is simplified,
and a new
2486 UnitsSet returned
with the results.
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
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)
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)
2513 """Check for dimensional equivalence between sets of units.
2515 Since all units in each set should be equivalent, we just compare
2516 an arbitrary member
from each set.
2518 other_units may be a single cellml_units instance,
in which case we
2519 compare an arbitrary member of self to it.
2522 u2 = other_units.extract()
2523 return u1.dimensionally_equivalent(u2)
2526 """Describe these units.
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.
2533 desc = list(set(u.description() for u
in self))
2536 desc =
u'{' +
u','.join(desc) +
u'}'
2543 Specialised units class.
2544 Contains useful methods
for defining the standard units dictionary,
2545 and checking
for units consistency.
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.
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.
2558 super(cellml_units, self).
__init__()
2567 return '<cellml_units %s @ 0x%x>' % (self.name, id(self))
2571 """Generate a tuple used as the basis for our hash value."""
2573 hash_list = [self.is_base_unit()]
2575 hash_list.append(self.name)
2576 hash_list.extend(list(getattr(self,
u'unit', [])))
2577 hash_tup = tuple(hash_list)
2584 """For avoiding duplicate identical units definitions.
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.
2589 l = [self.description(cellml=True)]
2590 if self.is_simple():
2592 l.append((self.unit.get_units_element().uniquify_tuple,
2597 """Generate a hash for these units.
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.
2608 """Compare 2 units objects, by comparing their hashes.
2610 Means using units as dictionary keys
is more sane.
"""
2611 if isinstance(other, cellml_units):
2612 if hash(self) == hash(other):
2615 return cmp(hash(self), hash(other))
2617 return super(cellml_units, self).
__cmp__(other)
2628 """Test if the relative difference of 2 values is within tol."""
2629 if abs(value1) == 0.0:
2630 return abs(value2) < tol
2632 return (
abs(value1-value2)/
abs(value1)) < tol
2635 """Compare 2 units elements for equality.
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).
2640 If both are simple units, they must also have the same offset.
2642 equal = isinstance(other, cellml_units) and \
2643 self.dimensionally_equivalent(other)
and \
2647 if equal
and self.is_simple():
2648 equal = self._rel_error_ok(self.unit.
get_offset(),
2649 other.unit.get_offset(),
2655 return self.rootNode.model
2658 """Return these units.
2660 Used for interface compatibility
with UnitsSet.
"""
2664 """Return a new UnitsSet containing this cellml_units object.
2666 Used for interface compatibility
with UnitsSet, where the method performs a shallow copy.
2671 """Return a human-readable name for these units.
2673 The name will be descriptive and based on the consituent <unit> elements, e.g.
'volt per second^2'
2675 By default,
if these are user-defined units, then
return self.name. Set force to
True to override this behaviour.
2677 Set cellml to
True to get a description that
is also a valid CellML identifier.
2679 if self.is_base_unit():
2681 elif not force
and not self._cml_generated:
2684 descs, per_descs = [], []
2686 m = self.get_multiplier()
2688 descs.append(unicode(m))
2690 dimensionless = self.get_units_by_name(
'dimensionless')
2691 for unit
in self.unit:
2692 if unit.get_units_element()
is dimensionless:
2694 desc = [getattr(unit,
u'prefix_',
u''), unit.get_units_element().name]
2695 e = unit.get_exponent()
2700 desc.extend([
'^', str(
abs(e))])
2701 desc =
u''.join(desc)
2703 per_descs.append(desc)
2708 descs =
u' '.join(descs)
2710 desc =
u' per '.join([descs] + per_descs)
2712 desc =
u'dimensionless'
2714 desc =
u"'" + desc +
u"'"
2717 desc =
u"'" + desc[1:] +
u"'"
2720 desc = desc.replace(
u"'",
u"").replace(
u"^",
u"")
2721 desc = desc.replace(
u"*",
u"").replace(
u".",
u"_")
2722 desc = desc.replace(
u" ",
u"_")
2726 """Return an object representing the element that defines the units named `uname'."""
2731 """Expand to a product of powers of base units.
2733 Expand this units definition according to the algorithm given in appendix C.3.4 of the CellML spec.
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.
2738 if self._cml_expanded
is None:
2740 if self.is_base_unit():
2742 self._cml_expanded = self
2743 elif self.is_simple():
2747 expanded_child = self.unit.get_units_element().
expand()
2751 if expanded_child.is_base_unit():
2756 ref_obj_new = expanded_child
2759 m2p2 = expanded_child.unit.get_multiplicative_factor()
2760 o2 = expanded_child.unit.get_offset()
2763 o_new = o1 + o2/m1p1
2766 ref_obj_new = expanded_child.unit.get_units_element()
2768 attrs = {
u'name': self.name}
2769 self._cml_expanded = self.xml_create_element(
u'units',
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'],
2780 unit._set_units_element(ref_obj_new)
2781 self._cml_expanded.xml_append(unit)
2786 attrs = {
u'name': self.name}
2787 exp_units = self.xml_create_element(
u'units', NSS[
u'cml'],
2789 exp_units._cml_generated =
True
2793 for unit
in self.unit:
2794 m_t *= unit.get_multiplicative_factor()
2797 e = unit.get_exponent()
2799 exp_u = unit.get_units_element().
expand()
2800 if exp_u.is_base_unit():
2802 attrs = {
u'units': exp_u.name,
2803 u'exponent': unicode(e)}
2804 u = self.xml_create_element(
u'unit', NSS[
u'cml'],
2806 exp_units.xml_append(u)
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',
2818 exp_units.xml_append(u_new)
2848 first_unit = sorted(exp_units.unit, key=
lambda u: u.units)[0]
2849 first_unit.multiplier = unicode(m_t)
2851 self._cml_expanded = exp_units
2853 self._cml_expanded.xml_parent = self.xml_parent
2855 return self._cml_expanded
2858 """Return True iff this is a base units definition."""
2859 return getattr(self,
u'base_units',
u'no') ==
u'yes'
2861 """Return True iff this is a simple units definition.
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
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():
2877 """Return the multiplicative factor of this units definition.
2879 The multiplicative factor of a units definition can be defined as
2880 the product of the multiplicative factors of its unit children.
2882 m = reduce(operator.mul,
2883 map(lambda unit: unit.get_multiplicative_factor(),
2884 getattr(self,
u'unit', [])),
2889 """Return the multiplier of this units definition.
2891 The multiplier of a units definition can be defined as the product
2892 of the multipliers of its unit children.
2894 return reduce(operator.mul,
2895 map(
lambda unit: unit.get_multiplier(),
2896 getattr(self,
u'unit', [])),
2900 """Return the offset associated with this units definition.
2902 If these are simple units, return the offset on our unit child.
2903 Otherwise,
return 0.
2905 if self.is_simple():
2912 def create_new(parent, name, bases, add_to_parent=False, standard=False):
2913 """Create a new units definition element.
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.
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.
2925 attrs = {
u'name': unicode(name)}
2927 attrs[
u'base_units'] =
u'yes'
2929 attrs[
u'standard'] =
u'yes'
2930 u = parent.xml_create_element(
u'units', NSS[
u'cml'], attributes=attrs)
2932 parent.xml_append(u)
2934 u.xml_parent = parent
2937 u._based_on(**basis)
2941 multiplier=None, offset=None):
2942 """Convenience function for defining new units."""
2943 for v
in [
'units',
'prefix',
'exponent',
'multiplier',
'offset']:
2945 exec
"if type(%s) == str: %s = unicode(%s)" % (v,v,v)
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}
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
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'],
2966 units_name_counter = [0]
2967 def simplify(self, other_units=None, other_exponent=1):
2968 """Simplify these units.
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.
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.
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.
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.
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.
2996 if isinstance(other_units, UnitsSet):
3001 if (other_units, other_exponent)
in self._cml_simplified:
3002 return self._cml_simplified[(other_units, other_exponent)]
3006 if self.is_base_unit():
3008 u = self.xml_create_element(
u'unit', NSS[
u'cml'],
3009 attributes={
u'units': self.name})
3011 u._set_units_element(self)
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():
3020 attrs = {
u'units': other_units.name,
3021 u'exponent': unicode(other_exponent)}
3022 u = self.xml_create_element(
u'unit', NSS[
u'cml'],
3024 u.xml_parent = other_units
3025 u._set_units_element(other_units)
3027 if other_exponent == 1:
3028 other_unit_elements = frozenset([u])
3030 if other_exponent == 1:
3031 units.extend(list(other_units.unit))
3032 other_unit_elements = frozenset(other_units.unit)
3034 for unit
in other_units.unit:
3038 u.exponent = unicode(other_exponent *
3040 u.multiplier = unicode(u.get_multiplier() **
3045 dimensionless = self.get_units_by_name(
u'dimensionless')
3046 d = {dimensionless: []}
3048 obj = unit.get_units_element()
3056 for obj
in d.keys():
3057 if obj != dimensionless
and len(d[obj]) > 1:
3059 expt = sum(map(
lambda u: u.get_exponent(), d[obj]))
3062 attrs = {
u'units':
u'dimensionless'}
3064 attrs = {
u'units': d[obj][0].units,
3065 u'exponent': unicode(expt)}
3068 m = reduce(operator.mul,
3069 map(
lambda u: u.get_multiplicative_factor(),
3071 attrs[
u'multiplier'] = unicode(m)
3073 new = self.xml_create_element(
u'unit', NSS[
u'cml'],
3075 new.xml_parent = self
3078 d[dimensionless].append(new)
3083 elif obj != dimensionless
and d[obj]:
3087 d[obj] = d[obj][0].clone()
3092 m = reduce(operator.mul,
3093 map(
lambda u: u.get_multiplicative_factor(),
3097 del d[dimensionless]
3101 d[dimensionless] = d[dimensionless][0].clone()
3102 d[dimensionless].multiplier = unicode(m)
3113 new_units = dimensionless
3116 unit_elements = set(d.values())
3117 if unit_elements == our_unit_elements:
3119 elif unit_elements == other_unit_elements:
3120 new_units = other_units
3123 self.units_name_counter[0] += 1
3124 uname =
u'___units_' + str(self.units_name_counter[0])
3126 new_units = self.xml_create_element(
3127 u'units', NSS[
u'cml'], attributes={
u'name': uname})
3128 new_units._cml_generated =
True
3130 new_units.xml_parent = self._best_parent(other_units)
3132 for unit
in unit_elements:
3133 new_units.xml_append(unit)
3134 if self.model._is_new_units_obj(new_units):
3137 new_units.xml_parent.add_units(uname, new_units)
3139 new_units = self.model._get_units_obj(new_units)
3140 self._cml_simplified[(other_units, other_exponent)] = new_units
3144 """Return a suitable parent for units formed from self and other_units.
3146 If either constituent is in a component, that component should be the
3147 parent, otherwise the model should be.
3149 p1, p2 = self.xml_parent, other_units and other_units.xml_parent
3150 if p2
and p1 != p2
and isinstance(p1, cellml_model):
3155 """Form the quotient of two units definitions.
3157 This method does not simplify the resulting units.
3158 Quotient units will be cached.
3160 if not other_units
in self._cml_quotients:
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)
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)
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]
3186 """Return True iff other_units is dimensionally equivalent to self.
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.
3194 if isinstance(other_units, UnitsSet):
3195 other_units = other_units.extract()
3196 u2 = other_units.expand().
simplify()
3199 if u1.is_base_unit():
3203 d1[u.get_units_element()] = u.get_exponent()
3204 if u2.is_base_unit():
3208 d2[u.get_units_element()] = u.get_exponent()
3210 sym_diff = set(d1.keys()) ^ set(d2.keys())
3212 dimensionless = self.get_units_by_name(
u'dimensionless')
3213 if not sym_diff == set([dimensionless]):
3220 if d1[k] != d2[k]:
return False
3229 """Specialised class for <unit> elements.
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.
3237 element_base.__init__(self)
3242 """Create a tuple to be used for hashing/equality tests."""
3248 """Compare two <unit> elements.
3250 Two <unit> elements are equal if they reference the same <units>
3251 element,
and have the same prefix, multiplier, etc.
3254 if isinstance(other, cellml_unit):
3255 eq = self.
_hash_tup() == other._hash_tup()
3258 """The inverse of self.__eq__(other)."""
3259 return not self.
__eq__(other)
3261 """Richer hashing function than the default based on object id.
3263 Returns the hash of a tuple of relevant attributes."""
3268 Return the object representing the <units> element that this <unit> element references.
3276 Set the object representing the <units> element that this <unit> element references.
3278 Don't use unless you know what you're doing.
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}
3292 """Return the factor this units reference is multiplied by.
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)
3301 p = getattr(self, u'prefix_', 0)
3310 """Return the multiplier of this units reference, as a float."""
3311 return float(getattr(self,
u'multiplier', 1))
3314 """Return the exponent of this units reference, as a float."""
3315 return float(getattr(self,
u'exponent', 1))
3318 """Return the offset of this units reference, as a float."""
3319 return float(getattr(self,
u'offset', 0))
3322 """Clone this object.
3324 Return a new <unit> element that has the same attributes as this
3329 attrs[aname] = getattr(self, apyname)
3330 new = self.xml_create_element(
u'unit', NSS[
u'cml'], attributes=attrs)
3333 new.xml_parent = self.xml_parent
3339 Exception class for errors raised trying to evaluate a MathML expression.
3345 Exception class for validation errors raised while checking mathematics.
3347 def __init__(self, context_obj, message, warn=False, level=None):
3348 """Create a mathematics validation error.
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.
3357 self.level = level or (logging.ERROR,logging.WARNING)[warn]
3366 if isinstance(context_obj, cellml_variable):
3370 expr_root = context_obj.xml_xpath(
u'ancestor-or-self::*[local-name(..)="math"]')[0]
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']:
3377 for elt
in math.xml_element_children(math.xml_parent):
3378 if elt
is math:
break
3379 if elt.localName ==
u'math':
3382 vref = context_obj.xml_xpath(
u'ancestor::cml:variable_ref')
3384 self.
reaction_spec =
' in mathematics for variable "%s" in a reaction' % vref[0].variable
3390 """Only show the XML where the error occurred."""
3396 return msg.encode(
'UTF-8')
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'
3405 return "%d%s" % (i, suf)
3408 if self.
warn: type =
'Warning'
3409 else: type =
'Error'
3410 msg = [type,
' ', where,
': ', self.
message]
3415 ' in component ', self.
cname,
'\n XPath: ',
3420 xml = self.
context.xml(indent =
u'yes',
3421 omitXmlDeclaration =
u'yes')
3422 msg = msg +
u'\n' + unicode(xml, encoding=
'UTF-8')
3431 Exception class for validation errors raised during
units checking
3434 def __init__(self, context_obj, message, warn=False, level=None):
3435 """Create a units validation error.
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.
3442 MathsError.__init__(self, context_obj, message, warn=warn, level=level)
3450 "Return the i'th child element of elt. Indexing starts at 1."
3452 for e
in elt.xml_children:
3453 if getattr(e,
'nodeType',
None) == Node.ELEMENT_NODE:
3458 raise ValueError(
"<"+elt.localName+
"> does not have "+str(i)+
3459 " child element(s).")
3461 "Get the first child element of elt."
3470 """Base class for units mixin classes."""
3472 """Add mathematics for an explicit units conversion.
3474 Wraps expr in the expression
3475 m[to_units/defn_units]*(expr-o1[defn_units]) + o2[to_units].
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):
3481 model._cml_conversions_needed =
True
3484 expr = expr.model._cml_special_units_converter(expr, defn_units, to_units)
3489 defn_units = expr.get_units().
extract(check_equality=
True)
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)
3495 defn_units_exp = defn_units.expand().
simplify()
3496 to_units_exp = to_units.expand().
simplify()
3498 m = (defn_units_exp.get_multiplicative_factor() / to_units_exp.get_multiplicative_factor())
3500 orig_expr, parent = expr, expr.xml_parent
3501 dummy = expr.xml_create_element(
u'dummy', NSS[
u'm'])
3503 parent.replace_child(expr, dummy)
3504 if defn_units_exp.get_offset() != 0:
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
3512 quotient_units = to_units.quotient(defn_units)
3514 uattr = orig_expr._ensure_units_exist(quotient_units, no_act=no_act)
3516 new_expr = mathml_apply.create_new(expr,
u'times', [(unicode(m), uattr), expr])
3517 new_expr._cml_units = to_units
3519 if to_units_exp.get_offset() != 0:
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
3527 if expr
is not orig_expr:
3528 model._cml_conversions_needed =
True
3531 parent.replace_child(dummy, expr)
3535 """Try to set the units of the given element.
3537 Generates a debug message if this isn
't possible.
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']:
3543 if boolean
is not units:
3546 elif elt.localName
in [
u'notanumber',
u'pi',
u'infinity',
u'exponentiale']:
3548 if dimensionless
is not units:
3553 'Cannot set units (to', units.description(),
') for element', elt.localName)
3557 """Contains the _set_in_units method for ci, cn, etc."""
3559 """Set the units this element should be expressed in.
3561 Where these aren't the units it's defined
in, replace self by suitable units conversion mathematics.
3563 defn_units = self.get_units(return_set=False)
3564 if defn_units != units:
3573 """Set the units of the application of this operator.
3575 The default behaviour for many operators
is to simply set all operands to have the given units.
3578 app = self.xml_parent
3581 for operand
in list(app.operands()):
3585 app._cml_units = units
3590 """Set the units of the application of this operator.
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.
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):
3605 if f < min_factor
or min_factor
is None:
3610 for operand
in list(app.operands()):
3615 app._cml_units = units
3620 """Set the units of the application of this operator.
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
3632 app = self.xml_parent
3633 min_factor, best_factor = None,
None
3634 least_units, best_units =
None,
None
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:
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:
3650 DEBUG(
'validator',
'\t<-',
3652 units.description())
3654 app._add_units_conversion(app, units, desired_units, no_act)
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)
3663 app._cml_units = units
3668 """Set the units of this element.
3670 For container elements, we set the units of the child(ren).
3674 for elt
in self.xml_children[:]:
3675 if getattr(elt,
'nodeType',
None) == Node.ELEMENT_NODE:
3683 """Base class for MathML elements."""
3692 return '<%s (%s) at 0x%x>' % (self.__class__.__name__, str(self), id(self))
3695 """Customise copying of MathML expressions.
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.
3701 new_elt = copy.copy(self)
3703 assert id(self)
not in memo
3704 memo[id(self)] = new_elt
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)
3711 new_dict[name_copy] = value
3712 new_elt.__dict__.update(new_dict)
3717 """Properly clone a MathML sub-expression.
3719 Makes sure siblings and parent don
't get copied too.
3721# print "Cloning MathML",
prid(expr,
True)
3722 next_elem, par = expr.next_elem, getattr(expr,
'xml_parent',
None)
3723 expr.next_elem =
None
3724 expr.xml_parent =
None
3725 new_expr = copy.deepcopy(expr)
3726 expr.next_elem = next_elem
3727 expr.xml_parent = par
3731 """Properly clone this expression.
3733 If register is True, then keep a link to this expression
in the clone.
3735 clone = mathml.clone(self)
3737 clone._cml_source_expr_for_clone = self
3742 If this is a clone
with a registered original expression,
return it.
3743 Otherwise returns
None.
3748 "Cache & return the enclosing component element."
3750 def get_ancestor(elt, name):
3751 while elt
and elt.localName != name:
3752 elt = elt.parentNode
3754 comp = get_ancestor(self,
u'component')
3759 solver_info = get_ancestor(self,
u'solver_info')
3763 raise ValueError(
"MathML element " + str(self) +
" does not occur in a model component!")
3765 component = property(get_component)
3768 """Forget cached component and variable references in this MathML tree.
3770 Used by partial evaluator when moving maths to a new component, and by
3771 simulation protocols.
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()
3786 """Cache & return the enclosing model element."""
3792 """Evaluate the given element.
3794 Tries to evaluate the given element, and raises an EvaluationError
3795 if this
is not possible.
3797 if hasattr(elt,
'evaluate')
and callable(elt.evaluate):
3798 return elt.evaluate()
3799 elif elt.localName ==
u'pi':
3808 """Ensure that there is an element in the XML tree giving this
3811 Add a new <units> element if this expression has generated units.
3813 If units
is not None, use the given units rather than those of
3816 Return an attribute dictionary
with the appropriate units
3820 return {(
u'cml:units', NSS[
u'cml']):
u'#UNUSED#'}
3823 units = self.get_units().
extract()
3825 units = self.model._get_units_obj(units)
3826 if units._cml_generated
and units.name[:3] ==
"___":
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
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:
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
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}
3850 attrs = {(
u'cml:units', NSS[
u'cml']):
u'#FUDGE#'}
3854 """Return the variable object for the given ci element.
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
3864 var = ci_elt.variable
3866 varname = unicode(ci_elt).strip()
3867 var = cellml_variable.get_variable_object(self.model, varname)
3871 """Return a list of 'variable' objects used in the given expression.
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).
3880 If an ODE appears, includes the mathml_apply instance defining
3881 the ODE. Otherwise all returned objects will be
3882 cellml_variable instances.
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))
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):
3912 """Transfer our complexity to the new element.
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.
3919 new_elt._cml_complexity = self._cml_complexity
3920 except AttributeError:
3924 """Adjust ancestor complexity because old_elt changed to new_elt.
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
3935 When this method
is called, only new_elt
is a child of self.
3943 new = new_elt._cml_complexity
3944 old = old_elt._cml_complexity
3945 except AttributeError:
3950 if isinstance(elt, mathml_piecewise):
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)
3959 elt._cml_complexity = ac
3960 elif hasattr(elt,
'_cml_complexity'):
3961 elt._cml_complexity += (new - old)
3962 elt = getattr(elt,
'xml_parent',
None)
3966 """Classify variables in the given expression according to how they are used.
3968 In the process, compute and return a set of variables on which that expression depends.
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
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.
3983 if hasattr(elt,
'classify_variables'):
3984 dependencies = elt.classify_variables(**kwargs)
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)
3997 super(mathml_math, self).
__init__()
4002 Base class for MathML constructor elements, e.g. apply
and piecewise.
4005 super(mathml_constructor, self).
__init__()
4010 Calculate a rough estimate of the computation time for
4011 evaluating the given element.
4013 If lookup_tables
is True, then assume we
're using lookup tables
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.
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)
4025 if kw[
'lookup_tables']
and \
4026 elt.getAttributeNS(NSS[
'lut'],
u'possible',
u'no') ==
u'yes':
4028 if hasattr(self.
rootNode,
'num_lookup_tables'):
4030 self.
rootNode.num_lookup_tables += 1
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
4045 elif elt.localName ==
'ci':
4046 if kw[
'algebraic']: ac[
'variable'] = 1
4048 elif elt.localName
in [
'degree',
'logbase']:
4053 if kw[
'store_result']
and isinstance(elt, mathml):
4054 elt._cml_complexity = ac
4058 """Helper method to get the binding time of a MathML element."""
4059 if hasattr(elt,
'_get_binding_time'):
4061 return elt._get_binding_time()
4062 elif elt.localName
in [
u'true',
u'false',
u'exponentiale',
u'pi']:
4063 return BINDING_TIMES.static
4066 " of element " + elt.localName)
4069 """Helper method to get the units of a MathML element."""
4070 if hasattr(elt,
'get_units'):
4073 elif hasattr(elt,
'_cml_units')
and elt._cml_units:
4078 if elt.localName
in [
u'false',
u'true']:
4082 elif elt.localName
in [
u'notanumber',
u'pi',
u'infinity',
4090 u'Unsupported element "', elt.localName,
'".']))
4096 """Try to reduce the given element.
4098 Call the _reduce method on elt, if it has one.
4099 If
not, do nothing (we assume elt cannot be reduced).
4101 if hasattr(elt,
'_reduce')
and callable(elt._reduce):
4104 DEBUG(
'partial-evaluator',
"Don't know how to reduce",
4109 """Evaluate self and return <cn>, <true> or <false>, as appropriate."""
4110 value = self.evaluate()
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'])
4118 new_elt = self.xml_create_element(
u'cn', NSS[
u'm'],
4119 content=unicode(
"%.17g" % value),
4124 """Update usage counts of variables used in the given expression.
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.
4130 if isinstance(expr, mathml_ci):
4132 expr.variable._decrement_usage_count()
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(),
4142 for e
in self.xml_element_children(expr):
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.
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'):
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]))
4178 raise ValueError(
'Unsupported type attribute for cn element: '
4181 val = float(unicode(self))
4185 """Return the binding time of this expression.
4187 The binding time of a <cn> element is always static,
4188 unless the CellML
is annotated otherwise.
4190 bt = self.getAttributeNS(NSS['pe'],
u'binding_time',
u'static')
4191 return getattr(BINDING_TIMES, bt)
4194 """Reduce this expression by evaluating its static parts.
4196 Is actually a no-op; we must have been annotated explicitly as dynamic.
4201 """Return the units this number is expressed in."""
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'],
4216 content=unicode(value))
4227 """Forget cached component and variable references in this MathML tree.
4229 Used by partial evaluator when moving maths to a new component, and by
4230 simulation protocols.
4237 """Cache & return the variable object refered to by this element."""
4239 vname = unicode(self).strip()
4245 """Set the variable object referred to by this element."""
4249 """Return the units of the variable represented by this element."""
4261 Evaluate this expression by returning the value of the
4262 variable it represents.
4267 """Return the binding time of this expression.
4269 The binding time of a <ci> element is that of the variable it
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))
4283 """Reduce this expression by evaluating its static parts.
4285 If this is a static variable, replace by its value (
as a <cn> element).
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.
4293 DEBUG('partial-evaluator',
"Reducing", self.
variable.fullname(),
4295 if bt
is BINDING_TIMES.static:
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),
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()
4307 defns = self.
variable.get_dependencies()
4313 if isinstance(defn, cellml_variable):
4316 DEBUG(
'partial-evaluator',
"Keeping",
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)
4329 self.
variable._decrement_usage_count(follow_maps=
False)
4332 elif isinstance(defn, mathml_apply):
4334 (self.
variable.get_usage_count() == 1
or
4335 self.
rootNode.partial_evaluator.is_instantiable(defn))):
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)
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())
4353 needs_special_treatment=lambda n: None):
4354 """Classify variables in this expression according to how they are used.
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.
4361 if not dependencies_only:
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))
4374 QUALIFIERS = frozenset((
'degree',
'bvar',
'logbase',
4375 'lowlimit',
'uplimit',
'interval',
'condition',
4376 'domainofapplication',
'momentabout'))
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'))
4394 super(mathml_apply, self).
__init__()
4399 """Clear the type, dependency, etc. information for this equation.
4401 This allows us to re-run the type & dependency analysis for the model.
"""
4409 """Return the list of variables this expression depends on."""
4413 """Return the element representing the operator being applied."""
4417 """Return True iff element is a qualifier element."""
4422 Return an iterable over the elements representing the qualifiers for
4425 quals = self.xml_element_children()
4430 Return an iterable over the elements representing the operands for
4434 operands = self.xml_element_children()
4441 Return an iterable containing a <units> element for each operand
4449 Evaluate this expression, and return its value,
if possible.
4453 if hasattr(op,
'evaluate')
and callable(op.evaluate):
4454 return op.evaluate()
4456 raise EvaluationError(
"Don't know how to evaluate the operator " + op.localName)
4460 Calculate a rough estimate of the computation time for
4461 evaluating this <apply> element.
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.
4467 If lookup_tables
is True, then assume we
're using lookup tables
4469 If algebraic is True, the complexity
is calculated
as a dictionary,
4470 mapping node types to the number of occurences of that type.
4472 kw['algebraic'] = kw.get(
'algebraic',
False)
4473 if kw[
'algebraic']: ac = {}
4477 op_name = self.
operator().localName
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
4487 if kw[
'algebraic']: ac[
'round'] = 1
4489 elif op_name
in OPS.elementary:
4490 if kw[
'algebraic']: ac[
'elementary'] = 1
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
4498 elif op_name ==
'power':
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
4505 if kw[
'algebraic']: ac[
'power'] = 1
4507 elif op_name ==
'root':
4508 if kw[
'algebraic']: ac[
'root'] = 1
4510 elif op_name ==
'diff':
4511 if kw[
'algebraic']: ac[
'variable'] = 1
4524 """Set the units this expression should be given in.
4526 If these aren't our natural units (as given by an initial
4527 get_units) then we need to add units conversion code.
4531 if units
is current_units:
4535 if units
in current_units:
4539 for src_units_set, src_units
in current_units._get_sources(units):
4540 expr = src_units_set.get_expression()
4543 if not done
and not no_act:
4549 if hasattr(op,
'_set_in_units')
and callable(op._set_in_units):
4550 op._set_in_units(units, no_act)
4553 "Don't know how to select units for operands of operator",
4554 op.localName,
"when its units are", units.description()]))
4557 """Recursively check this expression for dimensional consistency.
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.
4563 If yes, returns a <units> element
for the whole expression, based
4564 on the rules
in appendix C.3.3.
4566 Throws a UnitsError
if the units are inconsistent.
4574 operand_units_idx = itertools.izip(operand_units, itertools.count(1))
4579 if op
in self.
OPS.relations | self.
OPS.plusMinus:
4580 our_units = operand_units.next().
copy()
4582 if boolean
in our_units:
4584 u'Operator',op,
u'has boolean operands, which does not make sense.']))
4586 for u
in operand_units:
4587 if not hasattr(self.
model,
'_cml_special_units_converter')
and not our_units.dimensionally_equivalent(u):
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']))
4593 if op
in self.
OPS.relations:
4596 elif op
in self.
OPS.logical:
4598 for u, i
in operand_units_idx:
4599 if not boolean
in u:
4601 u'Operator',op,
u'requires operands to be booleans;',
4602 u'operand',str(i),
u'has units',u.description()]))
4605 elif op
in self.
OPS.elementary:
4607 for u, i
in operand_units_idx:
4608 if not u.dimensionally_equivalent(dimensionless):
4610 u'Operator',op,
u'requires operands to be dimensionless;',
4611 u'operand',str(i),
u'has units',u.description()]))
4614 if hasattr(self,
u'logbase'):
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()]))
4621 our_units =
UnitsSet([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()]))
4637 if arg_units.equals(dimensionless):
4638 our_units =
UnitsSet([dimensionless])
4645 raise UnitsError(self,
'Unable to units check power with an exponent that can vary at run-time',
4647 level=logging.WARNING_TRANSLATE_ERROR)
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)]),
4654 level=logging.WARNING_TRANSLATE_ERROR)
4655 our_units = dimensionless.simplify(arg_units, expt)
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'):
4664 if not u.dimensionally_equivalent(dimensionless):
4666 u'The degree qualifier must have dimensionless units, not',u.description()]))
4674 if not type(degree)
is float:
4677 raise UnitsError(self,
'Unable to units check root with a degree that can vary at run-time',
4679 level=logging.WARNING_TRANSLATE_ERROR)
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)]),
4685 level=logging.WARNING_TRANSLATE_ERROR)
4686 our_units = dimensionless.simplify(arg_units, 1/degree)
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)
4696 if not u.dimensionally_equivalent(dimensionless):
4698 u'The degree qualifier must have dimensionless units, not',u.description()]))
4702 raise UnitsError(self,
u'A diff operator must have a bvar qualifier')
4706 if not type(degree)
is float:
4709 raise UnitsError(self,
'Unable to units check derivative with a degree that can vary at run-time',
4711 level=logging.WARNING_TRANSLATE_ERROR)
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)]),
4717 level=logging.WARNING_TRANSLATE_ERROR)
4718 for e
in self.xml_element_children(self.bvar):
4719 if not e.localName ==
u'degree':
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:
4730 u'Operator',op,
u'has boolean operands, which does not make sense.']))
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':
4739 our_units = operand_units.next()
4740 our_units = our_units.simplify(other_units=operand_units.next(), other_exponent=-1)
4743 our_units = operand_units.next().
copy()
4746 raise UnitsError(self,
u' '.join([
u'Unsupported operator for units checking:', op]),
4748 level=logging.WARNING_TRANSLATE_ERROR)
4751 if isinstance(our_units, cellml_units):
4755 our_units.set_expression(self)
4759 """Check the current component owns the variable being assigned to.
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
4767 Adds to the model's error list if the check fails. This method
4768 always returns None.
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':
4776 var = first_operand.variable
4777 for iface
in [
u'public_interface',
u'private_interface']:
4778 if getattr(var, iface,
u'none') ==
u'in':
4780 u'Variable', var.fullname(),
4781 u'is assigned to in a math element, but has its',
4782 iface,
u'set to "in".']))
4785 dependencies_only=False,
4786 needs_special_treatment=lambda n: None):
4788 Classify variables in this expression according to how they are
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.
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
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.
4810 dependencies = set()
4811 ode_indep_var = None
4813 if op.localName ==
u'diff':
4816 dependencies.add((op.dependent_variable, op.independent_variable))
4817 if not dependencies_only:
4825 if lhs.localName ==
u'ci':
4828 var._add_dependency(self)
4830 if not dependencies_only:
4833 if t == VarTypes.Constant
or t == VarTypes.MaybeConstant:
4834 self.
model.validation_warning(
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(
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':
4848 diff = lhs.operator()
4849 if diff.localName ==
u'diff':
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)
4857 ode_indep_var = indep
4858 if not dependencies_only:
4863 raise MathsError(self,
u'Assignment statements are expected to be an ODE or assign to a variable.',
4865 level=logging.WARNING_TRANSLATE_ERROR)
4867 raise MathsError(self,
u'Assignment statements are expected to be an ODE or assign to a variable.',
4869 level=logging.WARNING_TRANSLATE_ERROR)
4873 dependencies.update(self.classify_child_variables(oper, dependencies_only=dependencies_only,
4874 needs_special_treatment=needs_special_treatment))
4881 if ode_indep_var
in dependencies:
4885 dependencies.add(ode_indep_var)
4893 """Test whether this is a top-level assignment expression."""
4897 """Return True iff this is the assignment of an ODE.
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.
4908 """Return True iff this is a straightforward assignment expression.
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.
4918 """Return the variable assigned to by this assignment.
4920 Should only be called on a top-level assignment expression.
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.
4926 If it
's an ODE, return a pair
4927 (dependent variable, independent variable).
4930 raise TypeError(
"not a top-level apply element")
4935 """Return the binding time of this expression.
4937 The binding time will be computed recursively and cached.
4938 It will also be made available
as an attribute
in the XML.
4940 It
is computed by taking the least upper bound of the binding
4941 times of our operands, unless the operator possesses an
4948 if hasattr(self,
u'binding_time'):
4954 if check_operator
and hasattr(op,
'_get_binding_time'):
4958 bts = [BINDING_TIMES.static]
4965 self.xml_set_attribute((
u'pe:binding_time', NSS[
u'pe']), unicode(self.
_cml_binding_time))
4969 """Reduce this expression by evaluating its static parts."""
4973 DEBUG(
'partial-evaluator',
"Reducing", op.localName, getattr(self,
u'id',
''))
4974 if check_operator
and hasattr(op,
'_reduce'):
4978 if bt == BINDING_TIMES.static:
4981 self._xfer_complexity(new_elt)
4982 self.replace_child(self, new_elt, self.xml_parent)
4985 elif bt == BINDING_TIMES.dynamic:
4993 """Create a new MathML apply element, with given content.
4995 elt should be any element in the document.
4997 operator
is used
as the name of the first, empty, child.
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.)
5005 qualifiers specifies a list of qualifier elements.
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)
5013 if isinstance(op, basestring):
5015 op = app.xml_create_element(
u'ci', NSS[
u'm'],
5016 content=unicode(op))
5017 elif isinstance(op, tuple):
5019 if isinstance(op[1], dict):
5022 attrs = {(
u'cml:units', NSS[
u'cml']): unicode(op[1])}
5023 op = app.xml_create_element(
u'cn', NSS[
u'm'],
5025 content=unicode(op[0]))
5034 super(mathml_piecewise, self).
__init__()
5040 Calculate a rough estimate of the computation time for
5041 evaluating this <piecewise> element.
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
5049 If lookup_tables
is True, then assume we
're using lookup tables
5051 If algebraic is True, the complexity
is calculated
as a dictionary,
5052 mapping node types to the number of occurences of that type.
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.
5058 kw['algebraic'] = kw.get(
'algebraic',
False)
5059 alg = kw[
'algebraic']
5060 if alg: ac, piece_dicts = {}, []
5063 count_lts = hasattr(self.
rootNode,
'num_lookup_tables')
and kw.get(
'count_tables',
True)
and not alg
5067 num_lts = self.
rootNode.num_lookup_tables
5070 for piece
in getattr(self,
u'piece', []):
5075 nlts = self.
rootNode.num_lookup_tables
5078 piece_dicts.append(piece_ac)
5081 piece_acs.append(piece_ac)
5083 piece_num_lts.append(self.
rootNode.num_lookup_tables - nlts)
5084 if hasattr(self,
u'otherwise'):
5086 nlts = self.
rootNode.num_lookup_tables
5089 piece_dicts.append(ow_ac)
5093 piece_acs.append(ow_ac)
5095 piece_num_lts.append(self.
rootNode.num_lookup_tables - nlts)
5096 max_idx, max_piece_ac =
max_i(piece_acs)
5102 self.
rootNode.num_lookup_tables -= sum(piece_num_lts)
5103 self.
rootNode.num_lookup_tables += piece_num_lts[max_idx]
5107 """Set the units this expression should be given in.
5109 This is done recursively by setting the units
for each option.
5111 We also set the units on each condition to be boolean, since
5112 subexpressions of the conditions may need units conversions added.
5119 for piece
in getattr(self,
u'piece', []):
5122 if hasattr(self,
u'otherwise'):
5126 """Recursively check this expression for dimensional consistency.
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.
5133 If consistent, returns a <units> element
for the whole expression.
5134 Throws a UnitsError
if the units are inconsistent.
5140 for piece
in getattr(self,
u'piece', []):
5143 if not boolean
in units:
5145 u'The second child element of a <piece> element must have'
5146 u'units of cellml:boolean, not',units.description()]))
5150 if hasattr(self,
u'otherwise'):
5151 value_elt =
child_i(self.otherwise, 1)
5153 for piece
in getattr(self,
u'piece', []):
5155 if our_units
is None:
5159 if not our_units.dimensionally_equivalent(units):
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(),
5165 our_units.update(units)
5167 if our_units
is None:
5169 u'A piecewise element must have at least one piece or',
5170 u'otherwise child in order to have defined units.']))
5173 our_units.set_expression(self)
5177 needs_special_treatment=lambda n: None):
5178 """Classify variables in this expression according to how they are used.
5180 In the process, compute and return a set of variables on which
5181 this expression depends.
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
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.
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))
5206 """Evaluate this piecewise expression.
5208 Tests choices in the order they occur
in the file.
5209 Only evaluates a choice
if its condition evaluates to
True.
5211 for piece
in getattr(self,
u'piece', []):
5213 cond_value = self.
eval(condition)
5214 if cond_value
is True:
5220 if hasattr(self,
u'otherwise'):
5224 "A piecewise element where the pieces aren't mutually",
5225 "exhaustive requires an otherwise element."]))
5229 """Return the binding time of this expression.
5231 The binding time will be computed recursively and cached.
5232 It will also be made available
as an attribute
in the XML.
5234 It
is computed by taking the least upper bound of the binding
5235 times of (some of) the conditions
and cases.
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.
5247 if hasattr(self,
u'binding_time'):
5253 bts = [BINDING_TIMES.static]
5254 for piece
in getattr(self,
u'piece', []):
5257 if bt
is BINDING_TIMES.static:
5258 cond_value = self.
eval(condition)
5259 if cond_value
is True:
5274 if hasattr(self,
u'otherwise'):
5281 self.xml_set_attribute((
u'pe:binding_time', NSS[
u'pe']),
5286 """Reduce this expression by evaluating its static parts.
5288 Even in a dynamic conditional, where a condition
is static
and
5289 evaluates to
False, the associated case
is discarded.
5298 deletable_pieces = []
5299 found_dynamic_piece =
False
5300 for piece
in getattr(self,
u'piece', []):
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:
5315 deletable_pieces.append(piece)
5316 elif bt
is BINDING_TIMES.dynamic:
5317 found_dynamic_piece =
True
5324 for piece
in deletable_pieces:
5326 self.xml_remove_child(piece)
5328 if hasattr(self,
u'otherwise'):
5329 if not found_dynamic_piece:
5332 new_elt =
child_i(self.otherwise, 1)
5337 if new_elt
is not None:
5339 for piece
in getattr(self,
u'piece', []):
5340 if not new_elt
is child_i(piece, 1):
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):
5352 self._xfer_complexity(new_elt)
5353 self.replace_child(self, new_elt, self.xml_parent)
5359 """Create a new piecewise element.
5361 elt is any element
in the current document.
5363 pieces
is a list of pairs of expressions: (case, condition).
5365 otherwise,
if given,
is the default case.
5367 piecewise = elt.xml_create_element(u'piecewise', NSS[
u'm'])
5368 for piece
in pieces:
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)
5378 otherwise_elt = elt.xml_create_element(
u'otherwise', NSS[
u'm'])
5379 otherwise_elt.xml_append(otherwise)
5380 piecewise.xml_append(otherwise_elt)
5385 """Class representing the MathML lambda construct.
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.
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)
5399 lambda_.xml_append(body_expr)
5404 """Base class for MathML operator elements."""
5406 """Raise an EvaluationError due to wrong operand count.
5408 found is the number of operands found; wanted
is a list of suitable
5409 numbers of operands.
5412 "Wrong number of operands for <", self.localName,
"> ",
5413 "(found ", str(found),
"; wanted",
' or '.join(map(str, wanted)),
")"]))
5417 """Class representing the diff element, containing some useful methods."""
5421 Return the variable object w.r.t which we are differentiating.
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
5433 Return the variable object being differentiated.
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
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'
5449 model = self.xml_parent.model
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)
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)
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.'
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)
5481 indep._set_type(VarTypes.Free)
5484 """Return the binding time of the enclosing <apply> element.
5486 This is the binding time of the expression defining this ODE.
5490 return expr._get_binding_time()
5493 """Reduce this expression by evaluating its static parts.
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.
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!
5504 app = self.xml_parent
5505 bt = app._get_binding_time()
5506 if bt == BINDING_TIMES.static:
5508 app._reduce(check_operator=
False)
5511 for ci
in [app.ci, app.bvar.ci]:
5512 ci._set_variable_obj(ci.variable.get_source_variable(recurse=
True))
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])
5526 """Reduce this expression by evaluating its static parts.
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.
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'):
5537 app._reduce(check_operator=
False)
5540 static_opers = filter(
lambda e: app._get_element_binding_time(e) == BINDING_TIMES.static,
5542 if len(static_opers) > 1:
5544 for oper
in static_opers:
5545 app.safe_remove_child(oper)
5547 new_expr = mathml_apply.create_new(self, self.localName, static_opers)
5549 app.xml_append(new_expr)
5552 app._reduce(check_operator=
False)
5555 """Class representing the MathML <plus> operator."""
5557 super(mathml_plus, self).
__init__()
5561 """Evaluate by summing the operands of the enclosing <apply>."""
5562 app = self.xml_parent
5563 ops = app.operands()
5566 value += self.
eval(operand)
5570 """Class representing the MathML <minus> operator."""
5572 """Evaluate the enclosing <apply> element.
5574 Behaviour depends on the number of operands: we perform
5575 either a unary or binary minus.
5577 app = self.xml_parent
5578 ops = list(app.operands())
5580 value = -self.
eval(ops[0])
5582 value = self.
eval(ops[0]) - self.
eval(ops[1])
5588 """Class representing the MathML <times> operator."""
5591 Evaluate by taking the product of the operands of the enclosing <apply>.
5593 app = self.xml_parent
5594 ops = app.operands()
5597 value *= self.
eval(operand)
5601 """Class representing the MathML <divide> operator."""
5603 """Evaluate by dividing the 2 operands of the enclosing <apply>."""
5604 app = self.xml_parent
5605 ops = list(app.operands())
5608 numer = self.
eval(ops[0])
5609 denom = self.
eval(ops[1])
5613 """Reduce this expression by evaluating its static parts.
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.
5619 app = self.xml_parent
5620 bt = app._get_binding_time()
5621 if bt == BINDING_TIMES.static:
5623 app._reduce(check_operator=
False)
5626 ops = list(app.operands())
5629 bt = app._get_element_binding_time(ops[1])
5630 if bt == BINDING_TIMES.static:
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'),
5637 app.replace_child(dummy, new_expr)
5638 app._reduce_elt(new_expr)
5640 times = self.xml_create_element(
u'times', NSS[
u'm'])
5641 app.replace_child(self, times)
5643 app._reduce(check_operator=
False)
5646 app._reduce(check_operator=
False)
5649 """Class representing the MathML <exp> operator."""
5651 """Return e to the power of the single operand."""
5652 app = self.xml_parent
5653 ops = list(app.operands())
5656 return math.exp(self.
eval(ops[0]))
5659 """Class representing the MathML <ln> operator."""
5661 """Return the natural logarithm of the single operand."""
5662 app = self.xml_parent
5663 ops = list(app.operands())
5666 return math.log(self.
eval(ops[0]))
5669 """Class representing the MathML <log> operator."""
5671 """Return the logarithm of the single operand."""
5672 app = self.xml_parent
5673 ops = list(app.operands())
5676 if hasattr(app,
u'logbase'):
5677 base = self.
eval(app.logbase)
5680 return math.log(self.
eval(ops[0]), base)
5683 """Class representing the MathML <abs> operator."""
5685 """Return the absolute value of the single operand."""
5686 app = self.xml_parent
5687 ops = list(app.operands())
5693 """Class representing the MathML <power> operator."""
5695 """Set the units of the application of this operator.
5697 Set the exponent to have units of dimensionless, and the operand to
5698 have an arbitrary member of its possible units set.
5700 Where these mean the <apply> doesn
't have the given units, wrap it
5701 in suitable units conversion mathematics.
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)
5709 app._cml_units = defn_units
5711 dimensionless = app.model.get_units_by_name(
'dimensionless')
5712 ops = list(app.operands())
5715 for src_units_set, src_units
in defn_units_set._get_sources(defn_units):
5716 expr = src_units_set.get_expression()
5721 """Return the first operand to the power of the second."""
5722 app = self.xml_parent
5723 ops = list(app.operands())
5726 return self.
eval(ops[0]) ** self.
eval(ops[1])
5729 """Reduce this expression by evaluating its static parts.
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.
5736 app = self.xml_parent
5737 bt = app._get_binding_time()
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]:
5746 app.safe_remove_child(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)
5758 app._reduce(check_operator=
False)
5761 """Class representing the MathML <root> operator."""
5763 """Set the units of the application of this operator.
5765 Set the degree to have units of dimensionless, and the operand to
5766 have an arbitrary member of its possible units set.
5768 Where these mean the <apply> doesn
't have the given units, wrap it
5769 in suitable units conversion mathematics.
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)
5777 app._cml_units = defn_units
5779 if hasattr(app,
u'degree'):
5780 dimensionless = app.model.get_units_by_name(
'dimensionless')
5783 for src_units_set, src_units
in defn_units_set._get_sources(defn_units):
5784 expr = src_units_set.get_expression()
5789 Return the operand to the given degree, if present.
5790 Otherwise
return the square root of the operand.
5792 app = self.xml_parent
5793 ops = list(app.operands())
5796 if hasattr(app,
u'degree'):
5797 degree = self.
eval(app.degree)
5800 return self.
eval(ops[0]) ** (1/degree)
5803 """Class representing the MathML <and> operator."""
5805 """Return the logical conjunction of the operands.
5807 Evaluates operands in the order given
in the file,
and will
5808 short-circuit at the first which evaluates to
False.
5810 app = self.xml_parent
5811 ops = app.operands()
5814 value = value
and self.
eval(operand)
5819 """Return the binding time of the enclosing <apply> element.
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.
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:
5842 """Class representing the MathML <or> operator."""
5844 """Return the logical disjunction of the operands.
5846 Evaluates operands in the order given
in the file,
and will
5847 short-circuit at the first which evaluates to
True.
5849 app = self.xml_parent
5850 ops = app.operands()
5853 value = value
or self.
eval(operand)
5858 """Return the binding time of the enclosing <apply> element.
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.
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:
5881 """Class representing the MathML <leq> operator."""
5884 Return True iff the value of the first operand
is
5885 less than
or equal to the value of the second.
5887 app = self.xml_parent
5888 ops = list(app.operands())
5891 return self.
eval(ops[0]) <= self.
eval(ops[1])
5894 """Class representing the MathML <lt> operator."""
5897 Return True iff the value of the first operand
is
5898 less than the value of the second.
5900 app = self.xml_parent
5901 ops = list(app.operands())
5904 return self.
eval(ops[0]) < self.
eval(ops[1])
5907 """Class representing the MathML <geq> operator."""
5910 Return True iff the value of the first operand
is
5911 greater than
or equal to the value of the second.
5913 app = self.xml_parent
5914 ops = list(app.operands())
5917 return self.
eval(ops[0]) >= self.
eval(ops[1])
5920 """Class representing the MathML <gt> operator."""
5923 Return True iff the value of the first operand
is
5924 greater than the value of the second.
5926 app = self.xml_parent
5927 ops = list(app.operands())
5930 return self.
eval(ops[0]) > self.
eval(ops[1])
5933 """Class representing the MathML <neq> operator."""
5935 """Evaluate the enclosing <apply> element.
5937 Return True iff the 2 operands are
not equal.
5939 app = self.xml_parent
5940 ops = list(app.operands())
5944 return (self.
eval(ops[0]) != self.
eval(ops[1]))
5947 """Class representing the MathML <eq> operator."""
5949 """Return True iff the enclosing <apply> is a top-level expression."""
5950 return self.xml_parent.is_top_level()
5953 """Set the units of the application of this operator.
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.
5959 ops = self.xml_parent.operands()
5961 lhs_units = lhs.get_units().
extract()
5965 self.xml_parent._cml_units = units
5970 """Evaluate the enclosing <apply> element.
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.
5976 If an assignment, evaluate the RHS, assign the value to
5977 the variable on the LHS,
and return it.
5979 If a comparison,
return True iff the 2 operands are equal.
5981 app = self.xml_parent
5982 ops = list(app.operands())
5988 value = self.
eval(ops[1])
5989 var = app.assigned_variable()
5990 if app.is_assignment():
5991 var.set_value(value)
5993 indepvar = var[1].get_source_variable(recurse=
True)
5994 var[0].set_value(value, ode=indepvar)
5999 value = (self.
eval(ops[0]) == self.
eval(ops[1]))
6003 """Return the binding time of the enclosing <apply> element.
6005 If this is a top-level expression, then only recurse into the RHS,
6006 otherwise proceed
as normal
for an apply.
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.
6012 app = self.xml_parent
6014 annotated_as_kept =
False
6016 DEBUG(
'partial-evaluator',
"BT ODE",
6017 map(
lambda v: v.fullname(), app.assigned_variable()))
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())
6027 bt = app._get_element_binding_time(rhs)
6028 if annotated_as_kept:
6029 bt = BINDING_TIMES.dynamic
6031 bt = app._get_binding_time(check_operator=
False)
6035 """Reduce this expression by evaluating its static parts.
6037 If this is a top-level assignment, then just reduce the RHS.
6038 Otherwise proceed
as normal
for an <apply>.
6040 app = self.xml_parent
6042 ops = list(app.operands())
6046 app._reduce_elt(rhs)
6048 app._reduce(check_operator=
False)
6052 """Return the right hand side of this expression.
6054 Should only be called if we
're actually an assignment.
6057 ops = self.xml_parent.operands()
6061 raise ValueError(
"Not an assignment expression.")
6064 """Return the left hand side of this expression.
6066 Should only be called if we
're actually an assignment.
6069 ops = self.xml_parent.operands()
6072 raise ValueError(
"Not an assignment expression.")
6075 """Class representing the MathML <rem> operator."""
6077 """Return the remainder when the first operand is divided by the second."""
6078 app = self.xml_parent
6079 ops = list(app.operands())
6082 return self.
eval(ops[0]) % self.
eval(ops[1])
6085 """Class representing the MathML <logbase> element."""
6087 """Evaluate this element, by evaluating its child."""
6091 """Class representing the MathML <degree> element."""
6093 """Evaluate this element, by evaluating its child."""
6097 """Class representing the MathML <otherwise> element.
6099 Only defined to make it inherit from mathml.
6104 """Class representing the MathML <piece> element.
6106 Only defined to make it inherit from mathml.
6111 """Class representing the MathML <sin> operator."""
6113 """Return the sine of the single operand."""
6114 app = self.xml_parent
6115 ops = list(app.operands())
6118 return math.sin(self.
eval(ops[0]))
6121 """Class representing the MathML <cos> operator."""
6123 """Return the cosine of the single operand."""
6124 app = self.xml_parent
6125 ops = list(app.operands())
6128 return math.cos(self.
eval(ops[0]))
6131 """Class representing the MathML <tan> operator."""
6133 """Return the tangent of the single operand."""
6134 app = self.xml_parent
6135 ops = list(app.operands())
6138 return math.tan(self.
eval(ops[0]))
6141 """Class representing the MathML <arcsin> operator."""
6143 """Return the arc sine of the single operand, in radians."""
6144 app = self.xml_parent
6145 ops = list(app.operands())
6148 return math.asin(self.
eval(ops[0]))
6151 """Class representing the MathML <arccos> operator."""
6153 """Return the arc cosine of the single operand, in radians."""
6154 app = self.xml_parent
6155 ops = list(app.operands())
6158 return math.acos(self.
eval(ops[0]))
6161 """Class representing the MathML <arctan> operator."""
6163 """Return the arc tangent of the single operand, in radians."""
6164 app = self.xml_parent
6165 ops = list(app.operands())
6168 return math.atan(self.
eval(ops[0]))
def __init__(self, context_obj, message, warn=False, level=None)
def show_xml_context_only(self)
def _generate_message(self, where)
def __init__(self, context_obj, message, warn=False, level=None)
def __new__(cls, iterable=[], expression=None)
def set_expression(self, expr)
def get_consistent_set(self, desired_units)
def extract(self, check_equality=False)
def __init__(self, iterable=[], expression=None)
def _add_source(self, units, src_units_set, src_units)
def _get_sources(self, units)
def simplify(self, other_units=None, other_exponent=1)
def dimensionally_equivalent(self, other_units)
def create_new(elt, name)
def _clear_hierarchy(self, reln_key)
def _has_child_components(self, reln_key)
def get_units_by_name(self, uname)
def get_variable_by_name(self, varname)
def parent(self, relationship=u 'encapsulation', namespace=None, name=None, reln_key=None)
def _add_variable(self, var)
def _set_parent_component(self, reln_key, parent)
def ignore_component_name(self)
def _build_units_dictionary(self)
def _add_child_component(self, reln_key, child)
def _del_variable(self, var, keep_annotations=False)
def add_units(self, name, units)
def find_state_vars(self)
def get_option(self, option_name)
def get_component_by_name(self, compname)
def _check_variable_units_exist(self)
def _check_variable_mappings(self)
def get_validation_warnings(self)
def add_units(self, name, units)
def get_variables_by_ontology_term(self, term)
def topological_sort(self, node)
def has_units(self, units)
def add_units_conversions(self)
def _report_exception(self, e, show_xml_context)
def clear_assignments(self)
def build_component_hierarchy(self, relationship, namespace=None, name=None, rels=None)
def _del_component(self, comp)
def _add_sorted_assignment(self, a)
def get_assignments(self)
_cml_sorting_variables_stack
def get_units_by_name(self, uname)
def _check_connection_units(self, check_for_units_conversions=False)
def _check_unit_cycles(self, unit)
def _is_new_units_obj(self, units)
def _remove_assignment(self, a)
def _classify_variables(self, assignment_exprs, xml_context=False)
def _order_variables(self, assignment_exprs, xml_context=False)
def get_config(self, config_attr=None)
def search_for_assignments(self, comp=None)
def get_validation_errors(self)
def _add_component(self, comp, special=False)
def _validate_component_hierarchies(self)
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)
def get_variable_by_cmeta_id(self, cmeta_id)
def build_name_dictionaries(self, rebuild=False)
def _validate_connection(self, conn)
def get_variable_by_name(self, compname, varname)
def do_binding_time_analysis(self)
def _get_units_obj(self, units)
def _add_units_obj(self, units)
def _check_cellml_subset(self, math_elts, root=True)
def xml(self, stream=None, writer=None, **wargs)
def _add_variable(self, var, varname, compname)
def _build_units_dictionary(self)
def validation_error(self, errmsg, level=logging.ERROR)
def get_standard_units(self)
def validation_warning(self, errmsg, level=logging.WARNING)
def _check_maths_name_references(self, expr, xml_context=False)
def calculate_extended_dependencies(self, nodes, prune=[], prune_deps=[], state_vars_depend_on_odes=False, state_vars_examined=set())
def _del_variable(self, varname, compname)
def _check_assigned_vars(self, assignments, xml_context=False)
def is_self_excitatory(self)
def _check_dimensional_consistency(self, assignment_exprs, xml_context=False, warn_on_units_errors=False, check_for_units_conversions=False)
def get_all_variables(self)
def get_variable_by_oxmeta_name(self, name, throw=True)
def get_units_element(self)
def get_multiplicative_factor(self)
def _set_units_element(self, obj, override=False)
def _add_dependency(self, dep)
def _set_type(self, var_type, _orig=None)
def set_value(self, value, ode=None, follow_maps=True)
def get_source_variable(self, recurse=False)
def set_pe_keep(self, keep)
def is_statically_const(self, ignore_annotations=False)
def _set_source_variable(self, src_var)
def remove_rdf_annotations(self, property=None)
def fullname(self, cellml=False, debug=False)
def set_is_modifiable_parameter(self, is_param)
def _get_binding_time(self)
def _add_ode_dependency(self, independent_var, expr)
def create_new(elt, name, units, id=None, initial_value=None, interfaces={})
def get_value(self, ode=None)
def get_variable_object(model, varname)
def is_output_variable(self)
def get_type(self, follow_maps=False)
def set_is_derived_quantity(self, is_dq)
def _reduce(self, update_usage=False)
def _unset_binding_time(self, only_temporary=False)
def get_ode_dependency(self, independent_var, context=None)
def get_dependencies(self)
def get_rdf_annotations(self, property)
def is_derived_quantity(self)
_cml_depends_on
Move the definition to this component defn._unset_cached_links() defn.xml_parent.xml_remove_child(def...
def get_rdf_annotation(self, property)
def _set_binding_time(self, bt, temporary=False)
def set_is_output_variable(self, is_ov)
def set_rdf_annotation_from_boolean(self, property, is_yes)
def get_all_expr_dependencies(self)
def clear_dependency_info(self)
def get_usage_count(self)
def is_modifiable_parameter(self)
def set_oxmeta_name(self, name)
def _update_ode_dependency(self, free_var, defn)
def _decrement_usage_count(self, follow_maps=True)
def add_rdf_annotation(self, property, target, allow_dup=False)
def xml_remove_child_at(self, index=-1)
def __setattr__(self, key, value)
def __delattr__(self, key)
def _get_binding_time(self)
def _get_binding_time(self, check_operator=True)
def _is_qualifier(self, element)
def _get_operand_units(self)
_cml_ode_has_free_var_on_rhs
def _reduce(self, check_operator=True)
def check_assigned_var(self)
def classify_variables(self, root=False, dependencies_only=False, needs_special_treatment=lambda n:None)
def assigned_variable(self)
def clear_dependency_info(self)
def create_new(elt, operator, operands=[], qualifiers=[])
def tree_complexity(self, **kw)
def _set_in_units(self, units, no_act=False)
def get_dependencies(self)
def _get_binding_time(self)
def get_units(self, return_set=True)
def _rename(self, new_name=None)
def _set_variable_obj(self, var)
def classify_variables(self, dependencies_only=False, needs_special_treatment=lambda n:None)
def _unset_cached_links(self, elt=None)
def create_new(elt, variable_name)
def create_new(elt, value, units)
def get_units(self, return_set=True)
def _get_binding_time(self)
def _update_usage_counts(self, expr, remove=False)
def _get_element_binding_time(self, elt)
def _get_element_units(self, elt, return_set=True)
def _tree_complexity(self, elt, **kw)
def _reduce_elt(self, elt)
def _get_binding_time(self)
def dependent_variable(self)
def independent_variable(self)
def create_new(elt, bvar, state_var, rhs)
def _set_in_units(self, units, no_act=False)
def _get_binding_time(self)
def create_new(elt, bound_var_names, body_expr)
def wrong_number_of_operands(self, found, wanted)
def _get_binding_time(self)
def create_new(elt, pieces, otherwise=None)
def classify_variables(self, dependencies_only=False, needs_special_treatment=lambda n:None)
def tree_complexity(self, **kw)
def _reduce(self, check_operator=True)
def _get_binding_time(self)
def _set_in_units(self, units, no_act=False)
def _set_in_units(self, units, no_act=False)
def _set_in_units(self, units, no_act=False)
def _set_in_units(self, desired_units, no_act=False)
def _set_in_units(self, units, no_act=False)
def _set_in_units(self, units, no_act=False)
def _set_in_units(self, units, no_act=False)
def _set_in_units(self, units, no_act=False)
def _set_element_in_units(self, elt, units, no_act=False)
def _add_units_conversion(self, expr, defn_units, to_units, no_act=False)
def clone_self(self, register=False)
_cml_source_expr_for_clone
def _unset_cached_links(self, elt=None)
def __deepcopy__(self, memo)
def get_original_of_clone(self)
def _ensure_units_exist(self, units=None, no_act=False)
def _xfer_complexity(self, new_elt)
def get_units_by_name(self, uname)
def amara_parse_cellml(source, uri=None, prefixes=None)
def simplify(self, other_units=None, other_exponent=1)
def _best_parent(self, other_units)
def create_new(parent, name, bases, add_to_parent=False, standard=False)
def _adjust_complexity(self, old_elt, new_elt)
def same_tree(self, other, this=None)
def check_append_safety(elt)
def _based_on(self, units=None, prefix=None, exponent=None, multiplier=None, offset=None)
def dimensionally_equivalent(self, other_units)
def quotient(self, other_units)
def description(self, force=False, cellml=False)
def get_multiplicative_factor(self)
def extract(self, check_equality=False)
def make_xml_binder()
Helpful utility functions #.
def classify_child_variables(self, elt, **kwargs)
def _rel_error_ok(self, value1, value2, tol)
def eq(self, other): """Compare 2 units elements for equality.
def add_methods_to_amara()
def amara_parse(source, uri=None, rules=None, binderobj=None, prefixes=None)
def prid(obj, show_cls=False)
def DEBUG(facility, *args)
scalarT< T > abs(scalarT< T > in)
scalarT< T > log(scalarT< T > in)