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')
220 self.connections_made.add(frozenset([src_var, target_var]))
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)
418 model_units = self.model.get_units_by_name(units.name)
422 assert units.uniquify_tuple == model_units.uniquify_tuple
425 units.name = self.
_uniquify_name(units.name, self.model.get_units_by_name)
426 self.model.add_units(units.name, units)
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.
745 assert not self._interface_component.ignore_component_name
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')
777 self._log_handler.flush()
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.
797 for from_units, to_units
in self.special_conversions.iterkeys():
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):
819 for from_units, to_units
in self.special_conversions.iterkeys():
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."""
891 boolean = self.model.get_units_by_name(
'cellml:boolean')
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)
923 self._converted_mappings.add(var_pair)
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 _find_connection_element
def _find_or_create_variable
def _create_connection_element
def _convert_initial_value
def add_special_conversion
def _add_all_odes_to_interface
_interface_component_name
def _transform_derivatives_on_rhs
def _check_special_conversion
def get_interface_component
def _transform_derivative_on_rhs
def make_var_computed_constant
def add_conversions_for_component
def _apply_special_conversion_for_nested_expr