1 """Copyright (c) 2005-2016, University of Oxford. 4 University of Oxford means the Chancellor, Masters and Scholars of the 5 University of Oxford, having an administrative office at Wellington 6 Square, Oxford OX1 2JD, UK. 8 This file is part of Chaste. 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions are met: 12 * Redistributions of source code must retain the above copyright notice, 13 this list of conditions and the following disclaimer. 14 * Redistributions in binary form must reproduce the above copyright notice, 15 this list of conditions and the following disclaimer in the documentation 16 and/or other materials provided with the distribution. 17 * Neither the name of the University of Oxford nor the names of its 18 contributors may be used to endorse or promote products derived from this 19 software without specific prior written permission. 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 25 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 27 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 30 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 This file contains various classes supporting modifications to CellML models. 43 """Error thrown if a model modification is invalid.""" 48 """Base class supporting common model modification functionality. 50 This class contains the logic to deal with adding/deleting variables, components, equations, etc. 51 and connecting things up. It also handles re-analysing the model when modifications have been 52 completed to ensure that PyCml's internal data structures are up-to-date. 54 Instances should be created with the model to modify as a single parameter. Once all 55 modifications have been completed, the finalize method must be called to ensure later 56 processing of the model (e.g. code generation) will succeed. 64 def finalize(self, error_handler, pre_units_check_hook=None, check_units=True):
65 """Re-do the model validation steps needed for further processing of the model. 67 Checks connections, etc. and builds up the dependency graph again, then performs 70 If any errors are found during re-validation, the error_handler will be called with the 71 list. Warnings are ignored. 73 TODO: figure out how to determine how much of this is actually needed - InterfaceGenerator 74 can probably get away with less work. 78 logging_info = validator.CellMLValidator.setup_logging(show_errors=
True, show_warnings=
False)
80 self.
model._check_variable_mappings()
81 if not self.
model._cml_validation_errors:
82 assignment_exprs = self.
model.search_for_assignments()
83 self.
model._check_assigned_vars(assignment_exprs)
84 if not self.
model._cml_validation_errors:
85 self.
model._classify_variables(assignment_exprs)
86 self.
model._order_variables(assignment_exprs)
87 if not self.
model._cml_validation_errors
and check_units:
88 if callable(pre_units_check_hook):
89 pre_units_check_hook()
90 self.
model._check_connection_units(check_for_units_conversions=
False)
91 self.
model._check_dimensional_consistency(assignment_exprs,
93 warn_on_units_errors=self.
model.get_option(
'warn_on_units_errors'),
94 check_for_units_conversions=
False)
95 if self.
model._cml_validation_errors:
96 error_handler(self.
model._cml_validation_errors)
98 validator.CellMLValidator.cleanup_logging(logging_info)
102 Clear cached links in the model, since we'll need to recompute many of them 103 once we've finished modifying it. Also clears dependency information. 105 for comp
in getattr(self.
model,
u'component', []):
106 for math
in getattr(comp,
u'math', []):
107 math._unset_cached_links()
108 for var
in self.
model.get_all_variables():
109 var.clear_dependency_info()
110 assignment_exprs = self.
model.search_for_assignments()
111 for expr
in assignment_exprs:
112 expr.clear_dependency_info()
115 """Create a new component in the model, ensuring the name is unique. 117 If a component with name cname already exists, 118 underscores will be added to the component name to make it unique. 122 self.
model.get_component_by_name(cname)
128 comp = cellml_component.create_new(self.
model, cname)
129 self.
model._add_component(comp)
133 """Create a connection between the given source and target variables. 135 The variables are both specified either by a pair (cname,vname), or as cellml_variable objects. 136 The source variable must exist within the model, whereas the target might not, in 137 which case it will be created. 139 Note that in the case that both source and target exist, it might NOT be the case that 140 target obtains its value from source. They might already be connected, and source obtains 141 its value from target. Or they might both obtain their value from a common source. 143 If the variable names are not identical, any variables created will have the same name as the 144 target, if possible. If there's an existing variable with that name, not connected to the 145 source, then underscores will be appended to the name to avoid conflicts. Note that we do 146 check for variables in intermediate components that have the same name as the source and are 147 connected to it, to avoid adding unnecessary variables. 149 Returns the target variable object. 151 if isinstance(source, cellml_variable):
152 src_cname, src_vname = source.component.name, source.name
154 src_cname, src_vname = source
155 if isinstance(target, cellml_variable):
156 target_cname, target_vname = target.component.name, target.name
158 target_cname, target_vname = target
159 src_comp = self.
model.get_component_by_name(src_cname)
160 target_comp = self.
model.get_component_by_name(target_cname)
161 if src_comp == target_comp:
162 return target_comp.get_variable_by_name(target_vname)
169 path = src_path[:meeting_index]
170 if src_path[meeting_index]:
171 path.append(src_path[meeting_index])
172 path.extend(reversed(target_path[:meeting_index]))
174 next_src_var = src_comp.get_variable_by_name(src_vname)
175 for i, src_comp
in enumerate(path[:-1]):
176 target_comp = path[i+1]
177 next_src_var = self.
_make_connection(next_src_var, target_comp, target_vname)
181 """Make a connection from a source variable to a given component and suggested local name. 183 Note that in the case that both variables already exist and are connected, the existing 184 connection is allowed to flow in either direction. 186 src_comp = src_var.component
189 if (target_var.get_type() == VarTypes.Mapped
190 and target_var.get_source_variable(recurse=
True)
is src_var.get_source_variable(recurse=
True)):
193 elif target_var.get_type() == VarTypes.Unknown:
195 assert not(hasattr(target_var,
u'initial_value'))
196 if src_comp
is target_comp.parent():
198 target_if =
u'public' 199 elif src_comp.parent()
is target_comp:
201 target_if =
u'private' 203 assert src_comp.parent()
is target_comp.parent()
205 target_if =
u'public' 209 if getattr(src_var, src_if +
u'_interface',
u'none') ==
u'in':
210 src_var = src_var.get_source_variable()
214 assert getattr(src_var, src_if +
u'_interface',
u'none') !=
u'in' 215 assert getattr(target_var, target_if +
u'_interface',
u'none') !=
u'out' 216 src_var.xml_set_attribute((src_if +
u'_interface',
None),
u'out')
217 target_var.xml_set_attribute((target_if +
u'_interface',
None),
u'in')
222 target_var._set_source_variable(src_var)
229 """Find any connection element containing a connection of the given variables. 231 Returns a pair, the first element of which is either the element or None, and the 232 second of which is a boolean indicating whether the variables need to be swapped 233 in order to match the order of the components in the connection. 235 cn1, cn2 = var1.component.name, var2.component.name
236 cnames = set([cn1, cn2])
237 for conn
in getattr(self.
model,
u'connection', []):
238 mc = conn.map_components
239 if set([mc.component_1, mc.component_2]) == cnames:
244 swap = conn.map_components.component_1 == cn2
250 """Create a connection element connecting the given variables and add to the model. 252 If there's already a connection element for the relevant pair of components, 253 we just add another map_variables element to that. 258 var1, var2 = var2, var1
260 conn = var1.xml_create_element(
u'connection', NSS[
u'cml'])
261 mapc = var1.xml_create_element(
u'map_components', NSS[
u'cml'],
262 attributes={
u'component_1': var1.component.name,
263 u'component_2': var2.component.name})
264 conn.xml_append(mapc)
265 self.
model.xml_append(conn)
266 mapv = var1.xml_create_element(
u'map_variables', NSS[
u'cml'],
267 attributes={
u'variable_1': var1.name,
268 u'variable_2': var2.name})
269 conn.xml_append(mapv)
272 """Remove a connection between two variables. 274 Removes the relevant map_variables element. 275 If this results in an empty connection element, removes that as well. 281 var1, var2 = var2, var1
283 mapv = conn.xml_xpath(
u'cml:map_variables[@variable_1="%s" and @variable_2="%s"]' 284 % (var1.name, var2.name))
287 conn.xml_remove_child(mapv[0])
288 if not hasattr(conn,
u'map_variables'):
289 conn.xml_parent.xml_remove_child(conn)
292 """Remove all connection elements for the given variable. 294 Removes each relevant map_variables element. 295 If this results in an empty connection element, removes that as well. 297 cname, vname = var.component.name, var.name
298 for conn
in list(getattr(self.
model,
u'connection', [])):
299 if cname == conn.map_components.component_1:
301 elif cname == conn.map_components.component_2:
305 for mapv
in conn.map_variables:
306 if vname == getattr(mapv, vid,
''):
308 conn.xml_remove_child(mapv)
309 if not hasattr(conn,
u'map_variables'):
310 conn.xml_parent.xml_remove_child(conn)
315 """Change all variables connected to oldVar to be mapped to newVar instead.""" 316 vars = [v
for v
in self.
model.get_all_variables()
if v.get_source_variable(
True)
is oldVar]
320 self.
del_attr(v,
u'public_interface')
321 self.
del_attr(v,
u'private_interface')
322 v.clear_dependency_info()
328 """Find the first element at which both lists are identical from then on.""" 331 while l1[i] == l2[i]:
341 """Return a path of components from that given to the encapsulation root. 343 The root is specified by None, since we're really dealing with a forest, 348 path.append(comp.parent())
353 """Apply func to any application of the given operator within the given tree.""" 354 for elt
in self.
model.xml_element_children(expr):
356 if isinstance(expr, mathml_apply)
and expr.operator().localName == operator:
357 func(expr, *args, **kwargs)
360 """Find a given variable in the model, creating it if necessary. 362 We look for a variable in the component named cname with the same name as the source. 363 If it doesn't exist, a variable named vname will be created in that component (unless 365 The variable will become a mapped variable with the given source. 366 Hence if it is created it will have the same units. 369 var = self.
model.get_variable_by_name(cname, source.name)
374 var = self.
model.get_variable_by_name(cname, vname)
377 units = source.component.get_units_by_name(source.units)
382 """Add a new variable to the given component. 384 Remaining arguments are as for cellml_variable.create_new. 385 Returns the new variable object. 387 if not isinstance(comp, cellml_component):
388 comp = self.
model.get_component_by_name(comp)
390 var = cellml_variable.create_new(comp, vname, units.name, **kwargs)
391 comp._add_variable(var)
395 """Helper function to convert a units specification into a cellml_units object. 397 The input can be a cellml_units object, in which case we just return it. 398 However, it can also be a serialised CellML units definition, in which case it 399 will be parsed to create the object. 401 if isinstance(units, cellml_units):
406 assert isinstance(units, cellml_units)
410 """Add a units definition to the model, if it doesn't already exist. 412 If the definition isn't in the model, at whole-model level, it will be added. If the same 413 definition is already available, however, that definition should be used by preference. 414 Will return the actual units object to use. 416 units = self.
model._get_units_obj(units)
422 assert units.uniquify_tuple == model_units.uniquify_tuple
427 self.
model.xml_append(units)
429 for unit
in getattr(units,
u'unit', []):
430 unit._set_units_element(self.
add_units(unit.get_units_element()), override=
True)
431 unit.units = unit.get_units_element().name
435 """Add an expression to the mathematics in the given component. 437 comp may be a cellml_component instance or a component name. 439 if not isinstance(comp, cellml_component):
440 comp = self.
model.get_component_by_name(comp)
441 if not hasattr(comp,
u'math'):
443 math = comp.xml_create_element(
u'math', NSS[
u'm'])
444 comp.xml_append(math)
446 comp.math.xml_append(expr)
449 """Remove an expression (ODE or assignment) from its parent.""" 450 assert isinstance(expr, mathml_apply)
452 expr.xml_parent.safe_remove_child(expr)
453 expr.xml_parent =
None 457 """Remove any existing definition (as an equation) of the given variable. 459 If keep_initial_value is False, then also remove any initial_value attribute. 461 If the variable is Mapped, throw a ModelModificationError. 463 if var.get_type() == VarTypes.Mapped:
464 raise ModelModificationError(
"Cannot remove the equation defining a mapped variable - remove the definition of its source instead")
465 if not keep_initial_value:
466 self.
del_attr(var,
u'initial_value')
469 for dep
in var.get_all_expr_dependencies():
472 var.clear_dependency_info()
475 """Delete an XML attribute from an element, if it exists.""" 476 for (pyname, (qname, ns_))
in elt.xml_attributes.items():
477 _, name = SplitQName(qname)
478 if ns_ == ns
and name == localName:
482 """Ensure varname is unique within the given component. 484 Underscores will be appended to the name until it is unique. The unique name will be returned. 489 """Ensure the given name is unique within a particular context. 491 The context is determined by the given function: it will be passed candidate names to test 492 for existence, and is expected to throw iff the name is not already used. Underscores will 493 be appended to the given name until callable throws, and the resulting unique name returned. 504 """Set the object used to units-convert variable initial values.""" 508 """Get the units converter object, if any has been set.""" 514 """Convert any initial value of the given variable into the given units. 516 If there is no initial value, returns None. 517 If there is no units converter, leaves the initial_value unchanged. 519 if not hasattr(var,
u'initial_value'):
521 value = var.initial_value
523 if not var.get_units().
equals(units):
525 value = self.
_units_converter.convert_constant(value, var.get_units(), units, var.component)
526 except EvaluationError, e:
529 return unicode(value)
534 """Class for generating an interface between a CellML model and external code. 536 This contains functionality for users to describe the interface desired by the external code, i.e. 537 which variables are inputs and/or outputs, and expected units. It will then create a new component 538 within the CellML model containing these variables, and add units conversions where required. The 539 external code then only needs to interact with this new component. 541 def __init__(self, model, name='interface', units_converter=None):
542 super(InterfaceGenerator, self).
__init__(model)
547 def add_input(self, var, units, annotate=True, convert_initial_value=True):
548 """Specify a variable as an input to the model. 550 var should be a cellml_variable object already existing in the model. 551 units should be a suitable input to self._get_units_object. 553 If adding both State and Free variables as inputs, make sure to add the Free variable first, 554 otherwise you will not be able to specify units for it. 556 Set annotate to False if you do not wish a Constant variable to be annotated as a modifiable 559 If a units converter has been supplied, we will also try to units-convert initial values. 560 This may not be possible if special conversions are used, since they may involve variables 561 whose values are not known at this time. If this is the case, set convert_initial_value to 562 False to avoid applying the conversion. A proper solution requires CellML 1.1 features. 564 The new variable added to the interface component is returned. 566 assert isinstance(var, cellml_variable)
568 var = var.get_source_variable(recurse=
True)
569 var_name = var.fullname(cellml=
True)
572 if t == VarTypes.Computed:
574 elif t
not in [VarTypes.Constant, VarTypes.Free, VarTypes.State]:
578 newvar = self.
add_variable(comp, var_name, units, id=var.cmeta_id,
580 interfaces={
u'public':
u'out'})
583 self.
del_attr(var,
u'initial_value')
584 self.
del_attr(var,
u'id', NSS[
'cmeta'])
586 if t == VarTypes.State:
589 if t == VarTypes.Constant
and annotate:
590 newvar.set_is_modifiable_parameter(
True)
596 """Specify a variable as an output of the model. 598 var should be a cellml_variable object already existing in the model. 599 units should be a suitable input to self._get_units_object. 600 The new variable will take the cmeta:id of the original, and hence existing metadata 601 annotations will refer to the new variable. 602 If annotate is set to True, the new variable will also be annotated as a derived quantity. 604 The new variable added to the interface component is returned. 606 assert isinstance(var, cellml_variable)
608 var = var.get_source_variable(recurse=
True)
609 var_name = var.fullname(cellml=
True)
611 newvar = self.
add_variable(comp, var_name, units, id=var.cmeta_id)
612 self.
del_attr(var,
u'id', NSS[
'cmeta'])
615 newvar.set_is_derived_quantity(
True)
619 """Add an output that's defined as a (MathML) function of existing model variables. 621 The desired units are those of the function's result. The function arguments will be 622 imported with their units as given by the model, and the function calculated. This result 623 will then be units-converted if necessary. 625 The new variable added to the interface component is returned. 631 result_var.set_pe_keep(
True)
635 operands.append(self.
add_output(var, var.get_units(), annotate=
False).name)
637 expr = mathml_apply.create_new(self.
model, operator, operands)
638 assign = mathml_apply.create_new(self.
model,
u'eq', [result_var.name, expr])
643 """Turn a variable into a constant.""" 645 var.clear_dependency_info()
646 var.initial_value = unicode(str(value))
647 var._set_type(VarTypes.Constant)
650 """Turn a variable into a Computed variable with constant value definition.""" 652 var.clear_dependency_info()
653 defn = mathml_apply.create_new(self.
model,
u'eq',
654 [var.name, (unicode(str(value)), var.get_units().name)])
656 var._set_type(VarTypes.Computed)
659 """Override finalize to also set up standard interface elements not defined individually.""" 662 super(InterfaceGenerator, self).
finalize(*args, **kwargs)
665 """Transform any equations with derivatives on the RHS to use the variable defining it instead. 667 self._split_ode must have been used for all derivatives before calling this method. This means 668 that each ODE now has a variable to which the RHS is assigned. Rather than using the derivative 669 directly, which could break the dependency chain if units conversions are used for time, equations 670 should refer to this new variable instead. 672 for expr
in self.
model.search_for_assignments():
676 """Transform a derivative on the RHS of an equation to refer to the defining variable. 678 Helper method used by self._transform_derivatives_on_rhs to do the actual transformation. 681 dep_var = expr.diff.dependent_variable.get_source_variable(recurse=
True)
682 indep_var = expr.diff.independent_variable.get_source_variable(recurse=
True)
683 ode = dep_var.get_ode_dependency(indep_var)
684 rhs_var = ode.eq.rhs.variable.get_source_variable(recurse=
True)
688 parent = expr.xml_parent
689 parent.xml_insert_after(expr, mathml_ci.create_new(parent, rhs_var.name))
690 parent.safe_remove_child(expr)
693 """Split an ODE definition so the derivative goes into the interface component. 695 The RHS stays where it is, and is assigned to a new variable, which is connected to the interface 696 component and assigned to the new derivative. newVar is the new state variable in the interface 697 component, and oldVar will soon be mapped to it by the caller. 699 Any other equations in the model which use the derivative are transformed to use the new variable 703 free_var = self.
model.find_free_vars()[0]
704 if free_var.component
is not newVar.component:
705 free_var = self.
add_input(free_var, free_var.get_units())
707 deriv_name = self.
_uniquify_var_name(
u'd_%s_d_%s' % (oldVar.name, free_var.name), oldVar.component)
708 orig_ode = oldVar.get_all_expr_dependencies()[0]
709 orig_rhs_var = self.
add_variable(oldVar.component, deriv_name, orig_ode.eq.lhs.get_units().
extract())
711 desired_units = newVar.get_units().
quotient(free_var.get_units())
712 mapped_rhs_var = self.
add_output(orig_rhs_var, desired_units, annotate=
False)
714 orig_rhs = orig_ode.eq.rhs
715 orig_ode.safe_remove_child(orig_rhs)
718 mathml_apply.create_new(self.
model,
u'eq',
719 [orig_rhs_var.name, orig_rhs]))
721 new_ode = mathml_diff.create_new(self.
model, free_var.name, newVar.name, mapped_rhs_var.name)
723 new_ode.classify_variables(root=
True, dependencies_only=
True)
726 """All the derivatives should be considered as model outputs, and state variables as model inputs. 728 For any that haven't been done explicitly, this method will add the corresponding state variable 729 as an input, with its original units, which has the desired effect. 732 for var
in self.
model.find_state_vars():
733 if var.component
is not comp:
737 """Get the new component that will contain the interface. 739 The name will be self._interface_component_name, unless a component with that name already exists, 740 in which case underscores will be added to the component name to make it unique. 750 """Top-level interface to the units conversion code in PyCml. 752 def __init__(self, model, warn_only=None, show_xml_context_only=False):
753 super(UnitsConverter, self).
__init__(model)
754 if warn_only
is None:
755 warn_only = model.get_option(
'warn_on_units_errors')
766 logger = logging.getLogger(
'units-converter')
767 logger.setLevel(logging.WARNING)
768 formatter = logging.Formatter(fmt=
"%(name)s: %(message)s")
769 handler = logging.StreamHandler(sys.stderr)
770 handler.setFormatter(formatter)
771 logger.addHandler(handler)
775 """Flush logger & remove handler.""" 776 logger = logging.getLogger(
'units-converter')
781 """Call the given function, and log any units errors produced.""" 783 func(*args, **kwargs)
784 except UnitsError, e:
786 e.show_xml_context_only()
789 e.level = logging.WARNING
790 logging.getLogger(
'units-converter').log(e.level, unicode(e).encode(
'UTF-8'))
793 """Apply a special conversion to the given (sub-)expression. 795 This will get called by mathml_units_mixin._add_units_conversion if a special conversion is required by a nested sub-expression. 798 if (from_units.dimensionally_equivalent(defn_units)
799 and to_units.dimensionally_equivalent(desired_units)):
802 DEBUG(
'units-converter',
"Used nested special conversion from", repr(from_units),
"to", repr(to_units))
809 """Check whether a special conversion applies to the given assignment. 811 Special conversions allow us to do units conversion between dimensionally non-equivalent 812 quantities, by utilising biological knowledge. Available special conversions are added 813 using the add_special_conversion method. 815 lhs_units = expr.eq.lhs.get_units()
816 rhs_units = expr.eq.rhs.get_units()
817 if lhs_units.dimensionally_equivalent(rhs_units):
820 if (from_units.dimensionally_equivalent(rhs_units)
821 and to_units.dimensionally_equivalent(lhs_units)):
824 DEBUG(
'units-converter',
"Used special conversion from", repr(from_units),
"to", repr(to_units))
830 """Add a new special conversion to the list available. 832 Special conversions allow us to do units conversion between dimensionally non-equivalent 833 quantities, by utilising biological knowledge. The function "converter" will be called with 834 an assignment (top-level mathml_apply instance) that has RHS units equivalent to from_units, 835 and LHS units equivalent to to_units. It should alter the equation in-place (i.e. the 836 object passed to it must contain the final equation) to do an appropriate units conversion, 837 at least so that LHS and RHS dimensions match. 842 """Helper method of use to special units conversions. 844 Will modify the given expr in-place, replacing the RHS by an application of the given operator. 845 The operands will be the existing RHS and a ci element referencing the supplied variable object. 846 Connections and variables will be added to ensure that the given variable is available in the 847 component in which expr appears. 849 Returns expr, for ease of chaining expressions. 851 assert isinstance(var, cellml_variable)
853 local_var_name = var.name
854 source_comp = var.component
855 expr_comp = expr.component
856 if source_comp != expr_comp:
857 local_var = self.
connect_variables(var, (expr_comp.name, var.fullname(cellml=
True)))
858 local_var_name = local_var.name
861 expr.safe_remove_child(rhs)
862 new_rhs = mathml_apply.create_new(var.model, operator, [rhs, local_var_name])
863 expr.xml_append(new_rhs)
867 """Helper method of use to special units conversions. 869 Will modify the given expr in-place, post-multiplying the RHS by a reference to the given variable object. 870 Connections and variables will be added to ensure that the given variable is available in the 871 component in which expr appears. 873 Returns expr, for ease of chaining expressions. 878 """Helper method of use to special units conversions. 880 Will modify the given expr in-place, post-dividing the RHS by a reference to the given variable 882 Connections and variables will be added to ensure that the given variable is available in the 883 component in which expr appears. 885 Returns expr, for ease of chaining expressions. 890 """Apply conversions to any assignments in the given iterable.""" 893 if isinstance(expr, mathml_apply):
900 """Convert a constant value into desired units.""" 903 expr = mathml_apply.create_new(self.
model,
u'eq', [(
u'0', to_units.name),
904 (unicode(value), from_units.name)])
907 expr._cml_assigns_to = expr.operands().next()
910 self.
try_convert(expr.eq.rhs._set_in_units, to_units)
912 return expr.eq.rhs.evaluate()
915 """Apply conversions to a mapping between two variables.""" 918 var_pair = frozenset([var1, var2])
920 DEBUG(
'units-converter',
'Skipping already converted mapping', var1,
'<->', var2)
927 if var2.get_source_variable()
is var1:
929 var1, var2 = var2, var1
930 comp1, comp2 = comp2, comp1
934 u1 = var1.get_units()
935 u2 = var2.get_units()
936 DEBUG(
'units-converter',
"Converting mapping of", var1,
":=", var2,
937 "(units:", repr(u1), repr(u2),
")")
938 if not u1.equals(u2):
941 if getattr(var1,
u'public_interface',
'') ==
u'in':
942 in_interface =
u'public' 944 in_interface =
u'private' 945 var1_converter = self.
add_variable(comp1, var1.name +
u'_converter', u2, interfaces={in_interface:
u'in'})
946 var1._cml_var_type = VarTypes.Computed
947 var1._cml_source_var =
None 948 delattr(var1, in_interface +
u'_interface')
949 var1_converter._set_source_variable(var2)
951 app = mathml_apply.create_new(model,
u'eq', [var1.name, var1_converter.name])
953 var1._cml_depends_on = [app]
954 app._cml_assigns_to = var1
957 mapping.variable_2 = var1_converter.name
959 mapping.variable_1 = var1_converter.name
961 var1_converter._used()
962 for _
in xrange(var1.get_usage_count()):
963 var2._decrement_usage_count()
967 assignments = model.get_assignments()
968 idx = assignments.index(var1)
969 assignments[idx:idx+1] = [var1_converter, app]
972 """Add units conversions for all connections in the given set. 974 :param connections: a set of variable pairs representing connections. For each pair of variables a units conversion 975 will be added if needed and not already performed. 978 for conn
in getattr(model,
u'connection', []):
979 comp1 = model.get_component_by_name(conn.map_components.component_1)
980 comp2 = model.get_component_by_name(conn.map_components.component_2)
981 for mapping
in conn.map_variables:
982 var1 = model.get_variable_by_name(comp1.name, mapping.variable_1)
983 var2 = model.get_variable_by_name(comp2.name, mapping.variable_2)
984 if frozenset([var1, var2])
in connections:
988 """Add all units conversions required by the given component. 990 This allows us to only apply the conversions required by an interface component created 991 by an InterfaceGenerator. 996 assignments = model.search_for_assignments(comp)
999 del self.
model._cml_special_units_converter
1000 for conn
in getattr(model,
u'connection', []):
1001 cname1 = conn.map_components.component_1
1002 cname2 = conn.map_components.component_2
1003 if comp.name
in [cname1, cname2]:
1004 comp1 = model.get_component_by_name(cname1)
1005 comp2 = model.get_component_by_name(cname2)
1006 for mapping
in conn.map_variables:
1007 var1 = model.get_variable_by_name(cname1, mapping.variable_1)
1008 var2 = model.get_variable_by_name(cname2, mapping.variable_2)
1012 """Add all units conversions required in the given model.""" 1017 for conn
in getattr(model,
u'connection', []):
1018 comp1 = model.get_component_by_name(conn.map_components.component_1)
1019 comp2 = model.get_component_by_name(conn.map_components.component_2)
1020 for mapping
in conn.map_variables:
1021 var1 = model.get_variable_by_name(comp1.name, mapping.variable_1)
1022 var2 = model.get_variable_by_name(comp2.name, mapping.variable_2)
def try_convert(self, func, args, kwargs)
def __init__(self, model, warn_only=None, show_xml_context_only=False)
def convert_assignments(self, exprs)
def amara_parse_cellml(source, uri=None, prefixes=None)
def modify_rhs(self, expr, operator, var)
def del_attr(self, elt, localName, ns=None)
def _find_common_tail(self, l1, l2)
def _transform_derivative_on_rhs(self, expr)
def finalize(self, error_handler, pre_units_check_hook=None, check_units=True)
def _find_connection_element(self, var1, var2)
def add_conversions_for_component(self, comp)
def _uniquify_var_name(self, varname, comp)
def add_special_conversion(self, from_units, to_units, converter)
def convert_mapping(self, mapping, comp1, comp2, var1, var2)
def add_output_function(self, resultName, operator, argVars, units)
def _convert_initial_value(self, var, units, do_conversion=True)
def divide_rhs_by(self, expr, var)
def _clear_model_caches(self)
def connect_variables(self, source, target)
def _transform_derivatives_on_rhs(self)
def get_units_converter(self)
def _parent_path(self, comp)
def add_all_conversions(self)
def convert_connections(self, connections)
def set_units_converter(self, converter)
def remove_definition(self, var, keep_initial_value=False)
def _add_all_odes_to_interface(self)
def remove_connections(self, var)
def get_interface_component(self)
def finalize(self, args, kwargs)
def __init__(self, model, name='interface', units_converter=None)
def add_expr_to_comp(self, comp, expr)
def _update_connections(self, oldVar, newVar)
def get_units_by_name(self, uname)
def times_rhs_by(self, expr, var)
_interface_component_name
def __init__(self, model)
def add_variable(self, comp, vname, units, kwargs)
def remove_expr(self, expr)
def _check_special_conversion(self, expr)
def _find_or_create_variable(self, cname, vname, source)
def extract(self, check_equality=False)
def create_new_component(self, cname)
def _split_ode(self, newVar, oldVar)
def make_var_constant(self, var, value)
def _uniquify_name(self, name, callable)
def convert_constant(self, value, from_units, to_units, comp)
def _create_connection_element(self, var1, var2)
def _cleanup_logger(self)
def add_input(self, var, units, annotate=True, convert_initial_value=True)
def DEBUG(facility, args)
def add_output(self, var, units, annotate=True)
def _make_connection(self, src_var, target_comp, target_vname)
def _get_units_object(self, units)
def quotient(self, other_units)
def make_var_computed_constant(self, var, value)
def _apply_special_conversion_for_nested_expr(self, expr, defn_units, desired_units)
def remove_connection(self, var1, var2)
def add_units(self, units)
def _process_operator(self, expr, operator, func, args, kwargs)