def _parse_rules(self, model, comp, node): """ Parses the rules (equations) in this model """ parent = node formulas = {} # Create variables with assignment rules (all except derivatives) node = dom_child(parent, 'assignmentRule') while node: var = self._convert_name(str(node.getAttribute('variable')).strip()) if var in comp: self.log('Parsing assignment rule for <' + str(var) + '>.') var = comp[var] var.set_rhs(parse_mathml_rhs( dom_child(node, 'math'), comp, self)) else: raise SBMLError('Assignment found for unknown parameter: "' + var + '".') node = dom_next(node, 'assignmentRule') # Create variables with rate rules (states) node = dom_child(parent, 'rateRule') while node: var = self._convert_name(str(node.getAttribute('variable')).strip()) if var in comp: self.log('Parsing rate rule for <' + var + '>.') var = comp[var] ini = var.rhs() ini = ini.eval() if ini else 0 var.promote(ini) var.set_rhs(parse_mathml_rhs( dom_child(node, 'math'), comp, self)) else: raise SBMLError('Derivative found for unknown parameter: <' + var + '>.') node = dom_next(node, 'rateRule')
def chain(kind, node, unary=None): """ Parses operands for chained operators (for example plus, minus, times and division). The argument ``kind`` must be the myokit expression type being parsed, ``node`` is a DOM node and ``unary``, if given, should be the unary expression type (unary Plus or unary Minus). """ ops = [] node = dom_next(node) while node: ops.append(parsex(node)) node = dom_next(node) n = len(ops) if n < 1: raise MathMLError('Operator needs at least one operand.') if n < 2: if unary: return unary(ops[0]) else: raise MathMLError('Operator needs at least two operands') ex = kind(ops[0], ops[1]) for i in range(2, n): ex = kind(ex, ops[i]) return ex
def _parse_units(self, model, comp, node): """ Parses custom unit definitions, creating a look-up table that can be used to convert these units to myokit ones. """ node = dom_child(node, 'unitDefinition') while node: name = node.getAttribute('id') self.log('Parsing unit definition for "' + name + '".') unit = myokit.units.dimensionless node2 = dom_child(node, 'listOfUnits') node2 = dom_child(node2, 'unit') while node2: kind = str(node2.getAttribute('kind')).strip() u2 = self._convert_unit(kind) if node2.hasAttribute('multiplier'): m = float(node2.getAttribute('multiplier')) else: m = 1.0 if node2.hasAttribute('scale'): m *= 10**float(node2.getAttribute('scale')) u2 *= m if node2.hasAttribute('exponent'): u2 **= float(node2.getAttribute('exponent')) unit *= u2 node2 = dom_next(node2, 'unit') self.units[name] = unit node = dom_next(node, 'unitDefinition')
def _parse_parameters(self, model, comp, node): """ Parses parameters """ node = dom_child(node, 'parameter') while node: # Create variable name = self._convert_name(str(node.getAttribute('id'))) self.log('Found parameter "' + name + '"') if name in comp: self.warn('Skipping duplicate parameter name: ' + str(name)) else: # Create variable unit = None if node.hasAttribute('units'): foreign_unit = node.getAttribute('units') if foreign_unit: unit = self._convert_unit(foreign_unit) value = None if node.hasAttribute('value'): value = node.getAttribute('value') var = comp.add_variable(name) var.set_unit(unit) var.set_rhs(value) node = dom_next(node, 'parameter')
def scan_encapsulated_children(parent, pcomp): """ Reads parent/child relationships from a <group> or <component_ref> tag and adds them to the dict ``parents``. Argument ``parent`` should be a <component_ref> tag and ``pcomp`` should be the corresponding cellml component object. """ kid = dom_child(parent, 'component_ref') while kid is not None: # Get cellml component from name try: comp = components[kid.getAttribute('component')] except KeyError: raise CellMLError('Group registered for unknown' ' component: ' + kid.getAttribute('component')) # Log relationship self.log('Component <' + comp.qname() + '> is encapsulated' ' in <' + pcomp.qname() + '>.') # Add relationship parents[comp] = pcomp # Scan kid for children scan_encapsulated_children(kid, comp) # Move to next kid kid = dom_next(kid, 'component_ref')
def _parse_parameters(self, model, comp, refs, node): """ Parses parameters """ node = dom_child(node, 'parameter') while node: # Create variable org_name = str(node.getAttribute('id')) name = self._convert_name(org_name) self.logger().log('Found parameter "' + name + '"') if name in comp: # pragma: no cover self.logger().warn('Skipping duplicate parameter name: ' + str(name)) else: # Create variable unit = None if node.hasAttribute('units'): foreign_unit = node.getAttribute('units') if foreign_unit: unit = self._convert_unit(foreign_unit) value = None if node.hasAttribute('value'): value = node.getAttribute('value') var = comp.add_variable(name) var.set_unit(unit) var.set_rhs(value) # Store reference to variable refs[org_name] = refs[name] = var node = dom_next(node, 'parameter')
def _parse_initial_assignments(self, model, comp, refs, node): """ Parses any initial values specified outside of the rules section. """ node = dom_child(node, 'initialAssignment') while node: var = str(node.getAttribute('symbol')).strip() var = self._convert_name(var) if var in comp: self.logger().log('Parsing initial assignment for "' + var + '".') var = comp[var] expr = parse_mathml_rhs(dom_child(node, 'math'), refs, self.logger()) if var.is_state(): # Initial value var.set_state_value(expr) else: # Change of value var.set_rhs(expr) else: # pragma: no cover raise SBMLError( # pragma: no cover 'Initial assignment found for unknown parameter <' + var + '>.') node = dom_next(node, 'initialAssignment')
def scan(parent): kid = dom_child(parent) while kid is not None: t = None if kid.namespaceURI == ns: t = self._flatten(kid) if t: b.write(t) b.write('\n') else: scan(kid) kid = dom_next(kid)
def parsex(node): """ Parses a mathml expression. """ def chain(kind, node, unary=None): """ Parses operands for chained operators (for example plus, minus, times and division). The argument ``kind`` must be the myokit expression type being parsed, ``node`` is a DOM node and ``unary``, if given, should be the unary expression type (unary Plus or unary Minus). """ ops = [] node = dom_next(node) while node: ops.append(parsex(node)) node = dom_next(node) n = len(ops) if n < 1: raise MathMLError('Operator needs at least one operand.') if n < 2: if unary: return unary(ops[0]) else: raise MathMLError('Operator needs at least two operands') ex = kind(ops[0], ops[1]) for i in range(2, n): ex = kind(ex, ops[i]) return ex # Start parsing name = node.tagName if name == 'apply': # Brackets, can be ignored in an expression tree. return parsex(dom_child(node)) elif name == 'ci': # Reference var = str(node.firstChild.data).strip() if var_table is not None: try: var = var_table[var] except KeyError: if logger: logger.warn('Unable to resolve reference to <' + str(var) + '>.') return myokit.Name(var) elif name == 'diff': # Derivative # Check time variable bvar = dom_next(node, 'bvar') if derivative_post_processor: derivative_post_processor(parsex(dom_child(bvar, 'ci'))) # Check degree, if given d = dom_child(bvar, 'degree') if d is not None: d = parsex(dom_child(d, 'cn')).eval() if not d == 1: raise MathMLError( 'Only derivatives of degree one are supported.') # Create derivative and return x = dom_next(node, 'ci') if x is None: raise MathMLError( 'Derivative of an expression found: only derivatives of' ' variables are supported.') return myokit.Derivative(parsex(x)) elif name == 'cn': # Number number = parse_mathml_number(node, logger) if number_post_processor: return number_post_processor(node, number) return number # # Algebra # elif name == 'plus': return chain(myokit.Plus, node, myokit.PrefixPlus) elif name == 'minus': return chain(myokit.Minus, node, myokit.PrefixMinus) elif name == 'times': return chain(myokit.Multiply, node) elif name == 'divide': return chain(myokit.Divide, node) # # Functions # elif name == 'exp': return myokit.Exp(parsex(dom_next(node))) elif name == 'ln': return myokit.Log(parsex(dom_next(node))) elif name == 'log': if dom_next(node).tagName != 'logbase': return myokit.Log10(parsex(dom_next(node))) else: return myokit.Log(parsex(dom_next(dom_next(node))), parsex(dom_child(dom_next(node)))) elif name == 'root': # Check degree, if given nxt = dom_next(node) if nxt.tagName == 'degree': # Degree given, return x^(1/d) unless d is 2 d = parsex(dom_child(nxt)) x = parsex(dom_next(nxt)) if d.is_literal() and d.eval() == 2: return myokit.Sqrt(x) return myokit.Power(x, myokit.Divide(myokit.Number(1), d)) else: return myokit.Sqrt(parsex(nxt)) elif name == 'power': n2 = dom_next(node) return myokit.Power(parsex(n2), parsex(dom_next(n2))) elif name == 'floor': return myokit.Floor(parsex(dom_next(node))) elif name == 'ceiling': return myokit.Ceil(parsex(dom_next(node))) elif name == 'abs': return myokit.Abs(parsex(dom_next(node))) elif name == 'quotient': n2 = dom_next(node) return myokit.Quotient(parsex(n2), parsex(dom_next(n2))) elif name == 'rem': n2 = dom_next(node) return myokit.Remainder(parsex(n2), parsex(dom_next(n2))) # # Trigonometry # elif name == 'sin': return myokit.Sin(parsex(dom_next(node))) elif name == 'cos': return myokit.Cos(parsex(dom_next(node))) elif name == 'tan': return myokit.Tan(parsex(dom_next(node))) elif name == 'arcsin': return myokit.ASin(parsex(dom_next(node))) elif name == 'arccos': return myokit.ACos(parsex(dom_next(node))) elif name == 'arctan': return myokit.ATan(parsex(dom_next(node))) # # Redundant trigonometry (CellML includes this) # elif name == 'csc': # Cosecant: csc(x) = 1 / sin(x) return myokit.Divide(myokit.Number(1), myokit.Sin(parsex(dom_next(node)))) elif name == 'sec': # Secant: sec(x) = 1 / cos(x) return myokit.Divide(myokit.Number(1), myokit.Cos(parsex(dom_next(node)))) elif name == 'cot': # Contangent: cot(x) = 1 / tan(x) return myokit.Divide(myokit.Number(1), myokit.Tan(parsex(dom_next(node)))) elif name == 'arccsc': # ArcCosecant: acsc(x) = asin(1/x) return myokit.ASin( myokit.Divide(myokit.Number(1), parsex(dom_next(node)))) elif name == 'arcsec': # ArcSecant: asec(x) = acos(1/x) return myokit.ACos( myokit.Divide(myokit.Number(1), parsex(dom_next(node)))) elif name == 'arccot': # ArcCotangent: acot(x) = atan(1/x) return myokit.ATan( myokit.Divide(myokit.Number(1), parsex(dom_next(node)))) # # Hyperbolic trigonometry (CellML again) # elif name == 'sinh': # Hyperbolic sine: sinh(x) = 0.5 * (e^x - e^-x) x = parsex(dom_next(node)) return myokit.Multiply( myokit.Number(0.5), myokit.Minus(myokit.Exp(x), myokit.Exp(myokit.PrefixMinus(x)))) elif name == 'cosh': # Hyperbolic cosine: cosh(x) = 0.5 * (e^x + e^-x) x = parsex(dom_next(node)) return myokit.Multiply( myokit.Number(0.5), myokit.Plus(myokit.Exp(x), myokit.Exp(myokit.PrefixMinus(x)))) elif name == 'tanh': # Hyperbolic tangent: tanh(x) = (e^2x - 1) / (e^2x + 1) x = parsex(dom_next(node)) e2x = myokit.Exp(myokit.Multiply(myokit.Number(2), x)) return myokit.Divide(myokit.Minus(e2x, myokit.Number(1)), myokit.Plus(e2x, myokit.Number(1))) # # Inverse hyperbolic trigonometry (CellML...) # elif name == 'arcsinh': # Inverse hyperbolic sine: asinh(x) = log(x + sqrt(1 + x*x)) x = parsex(dom_next(node)) return myokit.Log( myokit.Plus( x, myokit.Sqrt( myokit.Plus(myokit.Number(1), myokit.Multiply(x, x))))) elif name == 'arccosh': # Inverse hyperbolic cosine: # acosh(x) = log(x + sqrt(x + 1) * sqrt(x - 1)) x = parsex(dom_next(node)) return myokit.Log( myokit.Plus( x, myokit.Multiply( myokit.Sqrt(myokit.Plus(x, myokit.Number(1))), myokit.Sqrt(myokit.Minus(x, myokit.Number(1)))))) elif name == 'arctanh': # Inverse hyperbolic tangent: # atanh(x) = 0.5 * (log(1 + x) - log(1 - x)) x = parsex(dom_next(node)) return myokit.Multiply( myokit.Number(0.5), myokit.Minus(myokit.Log(myokit.Plus(myokit.Number(1), x)), myokit.Log(myokit.Minus(myokit.Number(1), x)))) # # Hyperbolic redundant trigonometry (CellML...) # elif name == 'csch': # Hyperbolic cosecant: csch(x) = 2 / (exp(x) - exp(-x)) x = parsex(dom_next(node)) return myokit.Divide( myokit.Number(2), myokit.Minus(myokit.Exp(x), myokit.Exp(myokit.PrefixMinus(x)))) elif name == 'sech': # Hyperbolic secant: sech(x) = 2 / (exp(x) + exp(-x)) x = parsex(dom_next(node)) return myokit.Divide( myokit.Number(2), myokit.Plus(myokit.Exp(x), myokit.Exp(myokit.PrefixMinus(x)))) elif name == 'coth': # Hyperbolic cotangent: # coth(x) = (exp(2*x) + 1) / (exp(2*x) - 1) x = parsex(dom_next(node)) e2x = myokit.Exp(myokit.Multiply(myokit.Number(2), x)) return myokit.Divide(myokit.Plus(e2x, myokit.Number(1)), myokit.Minus(e2x, myokit.Number(1))) # # Inverse hyperbolic redundant trigonometry (CellML has a lot to answer # for...) # elif name == 'arccsch': # Inverse hyperbolic cosecant: # arccsch(x) = log(sqrt(1/(x*x) + 1) + 1/x) x = parsex(dom_next(node)) return myokit.Log( myokit.Plus( myokit.Sqrt( myokit.Plus( myokit.Divide(myokit.Number(1), myokit.Multiply(x, x)), myokit.Number(1))), myokit.Divide(myokit.Number(1), x))) elif name == 'arcsech': # Inverse hyperbolic secant: # arcsech(x) = log(sqrt(1/(x*x) - 1) + 1/x) x = parsex(dom_next(node)) return myokit.Log( myokit.Plus( myokit.Sqrt( myokit.Minus( myokit.Divide(myokit.Number(1), myokit.Multiply(x, x)), myokit.Number(1))), myokit.Divide(myokit.Number(1), x))) elif name == 'arccoth': # Inverse hyperbolic cotangent: # arccoth(x) = 0.5 * (log(3 + 1) - log(3 - 1)) x = parsex(dom_next(node)) return myokit.Multiply( myokit.Number(0.5), myokit.Log( myokit.Divide(myokit.Plus(x, myokit.Number(1)), myokit.Minus(x, myokit.Number(1))))) # # Logic # elif name == 'and': return chain(myokit.And, node) elif name == 'or': return chain(myokit.Or, node) elif name == 'not': return chain(None, node, myokit.Not) elif name == 'eq' or name == 'equivalent': n2 = dom_next(node) return myokit.Equal(parsex(n2), parsex(dom_next(n2))) elif name == 'neq': n2 = dom_next(node) return myokit.NotEqual(parsex(n2), parsex(dom_next(n2))) elif name == 'gt': n2 = dom_next(node) return myokit.More(parsex(n2), parsex(dom_next(n2))) elif name == 'lt': n2 = dom_next(node) return myokit.Less(parsex(n2), parsex(dom_next(n2))) elif name == 'geq': n2 = dom_next(node) return myokit.MoreEqual(parsex(n2), parsex(dom_next(n2))) elif name == 'leq': n2 = dom_next(node) return myokit.LessEqual(parsex(n2), parsex(dom_next(n2))) elif name == 'piecewise': # Piecewise contains at least one piece, optionally contains an # "otherwise". Syntax doesn't ensure this statement makes sense. conds = [] funcs = [] other = None piece = dom_child(node) while piece: if piece.tagName == 'otherwise': if other is None: other = parsex(dom_child(piece)) elif logger: logger.warn( 'Multiple <otherwise> tags found in <piecewise>' ' statement.') elif piece.tagName == 'piece': n2 = dom_child(piece) funcs.append(parsex(n2)) conds.append(parsex(dom_next(n2))) elif logger: logger.warn('Unexpected tag type in <piecewise>: <' + piece.tagName + '>.') piece = dom_next(piece) if other is None: if logger: logger.warn('No <otherwise> tag found in <piecewise>') other = myokit.Number(0) # Create string of if statements args = [] f = iter(funcs) for c in conds: args.append(c) args.append(next(f)) args.append(other) return myokit.Piecewise(*args) # # Constants # elif name == 'pi': return myokit.Number('3.14159265358979323846') elif name == 'exponentiale': return myokit.Exp(myokit.Number(1)) elif name == 'true': # This is corrent, even in Python True == 1 but not True == 2 return myokit.Number(1) elif name == 'false': return myokit.Number(0) # # Unknown/unhandled elements # else: if logger: logger.warn('Unknown element: ' + name) ops = [] node = dom_child(node) if dom_child(node) else dom_next(node) while node: ops.append(parsex(node)) node = dom_next(node) return myokit.UnsupportedFunction(name, ops)
def _parse_model(self, dom): """ Parses a dom tree into a myokit model. """ model_tag = dom.getElementsByTagName('model')[0] name = self._sanitise_name(model_tag.getAttribute('name')) model = myokit.Model(name) model.meta['author'] = 'Cellml converter' # Parse model meta information desc = [] for tag in model_tag.getElementsByTagName('documentation'): desc.append(self._flatten(tag).strip()) desc = ('\n'.join(desc)).strip() if desc: model.meta['desc'] = desc # Check for unsupported CellML features # Check for <import> (allowed in CellML 1.1+) if model_tag.getElementsByTagName('import'): raise CellMLError('The CellML <import> tag is not supported.') # Check for <reaction> (allowed in any CellML) if model_tag.getElementsByTagName('import'): raise CellMLError('The CellML <import> tag is not supported.') # Check for <factorial> (allowed in all CellML, but why?) if model_tag.getElementsByTagName('factorial'): self.warn('The <factorial> tag is not supported.') # Check for MathML features not currently allowed in CellML # Check for <partialdiff> (not allowed in any CellML) if model_tag.getElementsByTagName('partialdiff'): self.warn('The <partialdiff> tag is not supported.') # Check for <sum> (not allowed in any CellML) if model_tag.getElementsByTagName('sum'): self.warn('The <sum> tag is not supported.') # Parse unit definitions si_units, munits, cunits = self._parse_units(model_tag) def convert_unit(unit): """ Parses a CellML unit (string) and returns a :class:`myokit.Unit` or ``None`` if successful, returns a string if conversion failed. """ if unit: if str(unit) in si_units: unit = si_units[unit] elif unit in munits: unit = munits[unit] elif unit in cunits[cname]: unit = cunits[cname][unit] else: unit = str(unit) return unit # Parse components components = {} # Dict of (component name, component) pairs for tag in model_tag.getElementsByTagName('component'): cname = tag.getAttribute('name') name = self._sanitise_name(cname) self.log('Parsing component: ' + name) comp = model.add_component(name) components[cname] = comp # Parse group relationships # There are a number of different group types. Myokit handles the # "encapsulation" type of grouping, the rest can be ignored without # introducing errors. # Dict of encapsulation relations (component, parent component) pairs parents = {} def scan_encapsulated_children(parent, pcomp): """ Reads parent/child relationships from a <group> or <component_ref> tag and adds them to the dict ``parents``. Argument ``parent`` should be a <component_ref> tag and ``pcomp`` should be the corresponding cellml component object. """ kid = dom_child(parent, 'component_ref') while kid is not None: # Get cellml component from name try: comp = components[kid.getAttribute('component')] except KeyError: raise CellMLError('Group registered for unknown' ' component: ' + kid.getAttribute('component')) # Log relationship self.log('Component <' + comp.qname() + '> is encapsulated' ' in <' + pcomp.qname() + '>.') # Add relationship parents[comp] = pcomp # Scan kid for children scan_encapsulated_children(kid, comp) # Move to next kid kid = dom_next(kid, 'component_ref') for group in model_tag.getElementsByTagName('group'): # Filter out encapsulation groups is_encapsulation = False for ref in group.getElementsByTagName('relationship_ref'): if ref.getAttribute('relationship') == 'encapsulation': is_encapsulation = True break if not is_encapsulation: continue # Parse and store relationships parent = dom_child(group, 'component_ref') while parent is not None: # Get cellml component from name try: pcomp = components[parent.getAttribute('component')] except KeyError: raise CellMLError('Group registered for unknown' ' component: ' + parent.getAttribute('component')) # Add kids scan_encapsulated_children(parent, pcomp) # Search for next parent parent = dom_next(parent, 'component_ref') # Parse variables references = {} # Dict (component name, (var name, var)) interfaces = {} # Dict (component name, (var name,(pub, pri, unit))) variables = {} # Dict (component name, (var name, variable)) values = {} # Dict (component name, (var name, variable value)) for ctag in model_tag.getElementsByTagName('component'): cname = ctag.getAttribute('name') comp = components[cname] references[cname] = rfs = {} interfaces[cname] = ifs = {} variables[cname] = vrs = {} values[cname] = vls = {} for vtag in ctag.getElementsByTagName('variable'): vname = vtag.getAttribute('name') # Get public and private interface pub = vtag.getAttribute('public_interface') pri = vtag.getAttribute('private_interface') if pub not in ('in', 'out'): pub = None if pri not in ('in', 'out'): pri = None # Get unit unit = convert_unit(vtag.getAttribute('units')) # Native variable? Then create if not (pub == 'in' or pri == 'in'): name = self._sanitise_name(vname) self.log('Parsing variable: ' + name) var = comp.add_variable(name) vrs[vname] = var init = str(vtag.getAttribute('initial_value')) if init != '': vls[vname] = init # Set unit if type(unit) == str: var.meta['cellml_unit'] = unit else: var.set_unit(unit) # Add resolved reference rfs[vname] = var else: # Otherwise, store as unresolved reference rfs[vname] = None # Store reference information ifs[vname] = (pub, pri, unit) # Parse connections for tag in model_tag.getElementsByTagName('connection'): # Find linked components map_components = tag.getElementsByTagName('map_components')[0] cname1 = map_components.getAttribute('component_1') cname2 = map_components.getAttribute('component_2') for comp in (cname1, cname2): if not comp in components: raise CellMLError('Connection found for unlisted' ' component: <' + comp + '>.') comp1 = components[cname1] comp2 = components[cname2] # If component is encapsulated, find parent try: par1 = parents[comp1] except KeyError: par1 = None try: par2 = parents[comp2] except KeyError: par2 = None # Get relevant lists for components ifs1 = interfaces[cname1] ifs2 = interfaces[cname2] rfs1 = references[cname1] rfs2 = references[cname2] # Find all references for pair in tag.getElementsByTagName('map_variables'): ref1 = pair.getAttribute('variable_1') ref2 = pair.getAttribute('variable_2') # Check interfaces try: int1 = ifs1[ref1] except KeyError: self.warn('No interface found for variable <' + str(ref1) + '>, unable to resolve connection.') break try: int2 = ifs2[ref2] except KeyError: self.warn('No interface found for variable <' + str(ref2) + '>, unable to resolve connection.') break # Determine direction of reference ref_to_one = None if int2[0] == 'in' and (par1 == par2 or par2 == comp1): # Reference from comp2 to its parent or sibling comp1 ref_to_one = True elif int1[0] == 'in' and (par1 == par2 or par1 == comp2): # Reference from comp1 to its parent or sibling comp2 ref_to_one = False elif int2[1] == 'in' and par1 == comp2: # Reference from comp2 to its child comp1 ref_to_one = True elif int1[1] == 'in' and par2 == comp1: # Reference from comp1 to its child comp2 ref_to_one = False else: self.warn('Unable to resolve connection between <' + str(ref1) + '> in ' + str(comp1) + '(' + str(int1[0]) + ', ' + str(int1[1]) + ') and <' + str(ref2) + '> in ' + str(comp2) + '(' + str(int2[0]) + ', ' + str(int2[1]) + ').') continue # Check units if int1[2] != int2[2]: self.warn('Unit mismatch between <' + str(ref1) + '> in ' + str(int1[2]) + ' and <' + str(ref2) + '> given in ' + str(int2[2]) + '.') # Now point reference at variable or reference in other comp try: ref = rfs1[ref1] if ref_to_one else rfs2[ref2] except KeyError: a, b = ref2, ref1 if ref_to_one else ref1, ref2 self.warn('Unable to resolve reference of ' + str(a) + ' to ' + str(b) + '.') continue if ref_to_one: rfs2[ref2] = (cname1, ref1) self.log('Variable <' + str(ref2) + '> in <' + str(cname2) + '> points at <' + str(ref1) + '> in <' + str(cname1) + '>.') else: rfs1[ref1] = (cname2, ref2) self.log('Variable <' + str(ref1) + '> in <' + str(cname1) + '> points at <' + str(ref2) + '> in <' + str(cname2) + '>.') # Check for references that are never connected for cname, rfs in references.iteritems(): for vname, ref in rfs.iteritems(): if ref is None: self.warn('Unresolved reference <' + str(vname) + '> in' ' component <' + str(cname) + '>. Creating a dummy' ' variable with this name.') c = components[cname] v = c.add_variable(vname) v.set_rhs(0) rfs[vname] = v # The references should now all point to either a variable or a # reference to another variable. In the next step, these are resolved. for cname, rfs in references.iteritems(): for vname, ref in rfs.iteritems(): if type(ref) == tuple: while True: ref = references[ref[0]][ref[1]] if type(ref) != tuple: break rfs[vname] = ref # MathML number post-processor to extract unit def npp(node, number): unit = convert_unit(node.getAttribute('cellml:units')) if unit: return myokit.Number(number.eval(), unit) else: return number # MathML derivative post-processor to check if we're only using time # derivatives global time time = None def dpp(lhs): var = lhs.var() global time if time is None: time = var elif time != var: raise CellMLError('Found derivatives to two different' ' variables: <' + str(time) + '> and <' + str(var) + '>.') # MathML expression parser def mathml(node, rfs): return parse_mathml_rhs(node, var_table=rfs, logger=self, number_post_processor=npp, derivative_post_processor=dpp) # Parse expressions for ctag in model_tag.getElementsByTagName('component'): cname = ctag.getAttribute('name') math = ctag.getElementsByTagName('math') vrs = variables[cname] vls = values[cname] rfs = references[cname] n = 0 for m in math: tag = dom_child(m) while tag: if tag.tagName != 'apply': raise CellMLError('Unexpected tag in expression: <' + tag.tagName + '>, expecting <apply>.') # First child of tag should be <eq /> eq = dom_child(tag, 'eq') if not eq: raise CellMLError('Unexpected content in math of' ' component <' + cname + '>.') # Get lhs and rhs tags lhs_tag = dom_next(eq) rhs_tag = dom_next(lhs_tag) # Check for partial derivatives if lhs_tag.tagName == 'apply': if dom_child(lhs_tag) == 'partialdiff': raise CellMLError( 'Unexpected tag in expression:' ' expecting <diff>, found <partialdiff>.' ' Partial derivatives are not supported.') # Parse lhs lhs = mathml(lhs_tag, rfs) if not isinstance(lhs, myokit.LhsExpression): raise CellMLError( 'Error parsing equation: Expecting' ' <ci> or <apply> after <eq> in "' + cname + '", got <' + str(lhs_tag.tagName) + '> instead.' ' Differential algebraic equations are not' ' supported).') # Check variable var = lhs.var() if var not in vrs.values(): raise CellMLError('Error: Equation found for unknown' ' variable <' + str(var) + '>.') # Check derivatives if lhs.is_derivative(): # Get CellML variable name vname = dom_child(lhs_tag, 'ci') vname = vname.firstChild.data.strip() # Promote variable try: i = float(vls[vname]) del (vls[vname]) except KeyError: self.warn('No initial value found for <' + var.qname() + '>.') i = 0 var.promote(i) # Parse rhs var.set_rhs(mathml(rhs_tag, rfs)) # Continue tag = dom_next(tag) n += 1 self.log('Found ' + str(n) + ' equations in ' + cname + '.') # Use remaining initial values (can be used to set constants) for cname, vls in values.iteritems(): vrs = variables[cname] for vname, val in vls.iteritems(): vrs[vname].set_rhs(myokit.Number(val)) # Bind time variable to engine time if time is not None: time.set_rhs(0) time.set_binding('time') # Check for variables with no rhs that are never referenced no_rhs = [v for v in model.variables(deep=True) if v.rhs() is None] no_rhs = set(no_rhs) for var in no_rhs: refs = set([x for x in var.refs_by()]) if len(refs) == 0 or refs in no_rhs: self.warn('No expression for variable <' + var.qname() + '> is' ' defined and no other variables reference it. The' ' variable will be removed.') var.parent().remove_variable(var) else: self.warn('No expression for variable <' + var.qname() + '> is' ' defined. This variable will be set to zero.') var.set_rhs(myokit.Number(0)) return model
def _parse_units(self, model_tag): """ Parses all cellml units into myokit units. Returns a tuple (munits, cunits) where munits is a dict mapping cellml unit names to myokit unit objects (or None objects if a unit couldn't be parsed). The cunits part maps cellml component names to dicts of the same structure. """ # <units> Can be placed inside <model>, <component> or <import> # for <model> and <import> the units are global. # The <import> tag is not supported by this importer. # A Units tag can set base_units="yes" to define a new base unit: this # is not supported. si_units = { 'dimensionless': myokit.units.dimensionless, 'ampere': myokit.units.A, 'farad': myokit.units.F, 'katal': myokit.units.kat, 'lux': myokit.units.lux, 'pascal': myokit.units.Pa, 'tesla': myokit.units.T, 'becquerel': myokit.units.Bq, 'gram': myokit.units.g, 'kelvin': myokit.units.K, 'meter': myokit.units.m, 'radian': myokit.units.rad, 'volt': myokit.units.V, 'candela': myokit.units.cd, 'gray': myokit.units.Gy, 'kilogram': myokit.units.kg, 'metre': myokit.units.m, 'second': myokit.units.s, 'watt': myokit.units.W, 'celsius': myokit.units.C, 'henry': myokit.units.H, 'liter': myokit.units.L, 'mole': myokit.units.mol, 'siemens': myokit.units.S, 'weber': myokit.units.Wb, 'coulomb': myokit.units.C, 'hertz': myokit.units.Hz, 'litre': myokit.units.L, 'newton': myokit.units.N, 'sievert': myokit.units.Sv, 'joule': myokit.units.J, 'lumen': myokit.units.lm, 'ohm': myokit.units.R, 'steradian': myokit.units.sr, } si_prefixes = { 'yotta': 1e24, 'zetta': 1e21, 'exa': 1e18, 'peta': 1e15, 'tera': 1e12, 'giga': 1e9, 'mega': 1e6, 'kilo': 1e3, 'hecto': 1e2, 'deka': 1e1, 'deci': 1e-1, 'centi': 1e-2, 'milli': 1e-3, 'micro': 1e-6, 'nano': 1e-9, 'pico': 1e-12, 'femto': 1e-15, 'atto': 1e-18, 'zepto': 1e-21, 'yocto': 1e-24, } class Unit: def __init__(self, name): self.name = name self.parts = [] class Part: def __init__(self, base): self.base = base self.prefix = None self.multiplier = None self.exponent = None def parse(tag): """ Parses <units> tags into Unit objects. """ name = tag.getAttribute('name') self.log('Parsing unit: ' + name) unit = Unit(name) for part in tag.getElementsByTagName('unit'): if part.hasAttribute('offset'): self.warn('The "offset" attribute for <unit> tags is not' ' supported.') p = Part(part.getAttribute('units')) x = part.getAttribute('prefix') if x: p.prefix = str(x) x = part.getAttribute('multiplier') if x <> '': p.multiplier = float(x) x = part.getAttribute('exponent') if x <> '': p.exponent = float(x) unit.parts.append(p) return unit # Parse units in model munits = [] tag = dom_child(model_tag, 'units') while tag: munits.append(parse(tag)) tag = dom_next(tag, 'units') # Parse units in components cunits = {} for tag in model_tag.getElementsByTagName('component'): cunits[tag.getAttribute('name')] = units = [] for unit in tag.getElementsByTagName('units'): units.append(parse(unit)) # Order units (units can refer to each other in a DAG form) def order(units, global_units=None): """ Orders a list of (name, parts) tuples so that none of the parts refer to a unit defined later in the list. Returns an odict mapping names to (name, parts) tuples. """ todo = units units = odict() # List units that can already be referenced at this point okay = si_units.keys() if global_units: for name, unit in global_units: okay.append(name) # Run through todo list while todo: done = [] for unit in todo: ok = True for part in unit.parts: if part.base not in okay: ok = False break if ok: done.append(unit) okay.append(unit.name) for unit in done: units[unit.name] = unit todo.remove(unit) if len(done) == 0: break if todo: # Unable to resolve all units for unit in todo: self.warn('Unable to resolve unit: ' + str(unit.name)) units[unit.name] = unit return units munits = order(munits) for name, units in cunits.iteritems(): cunits[name] = order(units) # Convert units def convert(obj, local_map=None): """ Converts a Unit object to a myokit unit. """ base = myokit.units.dimensionless for part in obj.parts: # Get simple unit if str(part.base) in si_units: unit = si_units[part.base] elif part.base in munits: unit = munits[part.base] elif local_map and part.base in local_map: unit = local_map[part.base] else: self.warn('Unknown base unit: ' + str(part.base)) return None # Add prefix if part.prefix is not None: try: unit *= si_prefixes[part.prefix] except KeyError: try: if str(part.prefix) == str(int(part.prefix)): unit *= 10**int(part.prefix) else: raise ValueError except ValueError: self.warn( 'Unknown prefix in unit specification: "' + str(part.prefix) + '".') return None # Exponent (prefix part is exponentiated, multiplier is not) if part.exponent is not None: e = int(part.exponent) if e - part.exponent > 1e-15: self.warn( 'Non-integer exponents in unit specification' + ' are not supported: ' + str(part.exponent)) return None unit **= e # Multiplier if part.multiplier is not None: unit *= part.multiplier # Multiply base unit with this one base *= unit self.log('Converted unit "' + obj.name + '" to ' + str(base)) return base # Convert all units in <model> for name, obj in munits.iteritems(): munits[name] = convert(obj) # Convert all units in components for cname, units in cunits.iteritems(): for name, obj in units.iteritems(): units[name] = convert(obj, units) # Return unit maps return si_units, munits, cunits