コード例 #1
0
    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')
コード例 #2
0
ファイル: _importer.py プロジェクト: teosbpl/myokit
 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')
コード例 #3
0
 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')
コード例 #4
0
def parse_mathml(s):
    """
    Parses a mathml string that should contain a single expression.
    """
    import xml.dom.minidom
    x = xml.dom.minidom.parseString(s)
    return parse_mathml_rhs(dom_child(x))
コード例 #5
0
ファイル: _importer.py プロジェクト: teosbpl/myokit
 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')
コード例 #6
0
        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')
コード例 #7
0
    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')
コード例 #8
0
 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)
コード例 #9
0
def parse_mathml_rhs(node,
                     var_table=None,
                     logger=None,
                     number_post_processor=None,
                     derivative_post_processor=None):
    """
    Takes a MathML node ``node`` (using the ``xml.dom.Node`` interface) and
    parses its contents into a :class:`myokit.Expression`.

    Not all of MathML is supported (so no integrals, set theory etc.) but only
    a subset common to electrophysiology. In addition, some not-so-common
    elements are supported because they're allowed appear in
    :class:`CellML <myokit.formats.cellml.CellMLImporter>` documents.

    Variable names will be returned as strings, unless the optional dict
    argument ``var_table`` is given. Note that the :class:`myokit.VarOwner`
    classes support the dict interface.

    If the argument ``logger`` is given this will be used to log messages to,
    assuming the :class:`myokit.formats.TextLogger` interface.

    Optional post-processing of numbers (``<cn>`` tags) can be added by passing
    in a callable ``number_post_processor(tag, number)``. This will be called
    after parsing each ``<cn>`` tag with the original node as the first
    argument (as an ``xml.dom.minidom`` node), and the created number object as
    the second (as a :class:`myokit.Number`). The function must return a new
    :class:`myokit.Number` object.

    Optional checking of derivatives (``<diff>`` tags) can be added by passing
    in a callable ``derivative_post_processor(time)``. This will be called with
    the :class:`myokit.Name` representing the variable with respect to which
    the derivative is being taken. This allows importers to ensure only
    time-derivatives are being loaded.

    The following MathML elements are recognised:

    Literals and references

    ``<ci>``
        Becomes a :class:`myokit.Name`.
    ``<diff>`` (with ``<bvar>`` and ``<degree>``)
        Becomes a :class:`myokit.Derivative`. Only first-order derivatives are
        supported. To check if the derivatives are all time-derivatives, the
        derivative post-processing function can be used.
    ``<cn>``
        Becomes a :class:`myokit.Number`. To process units which may be present
        in the tag's attributes (esp. in CellML) the number post-processing
        function can be used.

    Algebra

    ``<plus>``
        Becomes a :class:`myokit.PrefixPlus`, a :class`myokit.Plus` or a tree
        of :class:`myokit.Plus` elements.
    ``<minus>``
        Becomes a :class:`myokit.PrefixMinus`, a :class`myokit.Minus` or a tree
        of :class:`myokit.Minus` elements.
    ``<times>``
        Becomes a :class:`myokit.Multiply` or a tree of
        :class:`myokit.Multiply` elements.
    ``<divide>``
        Becomes a :class:`myokit.Divide` or a tree of :class:`myokit.Divide`
        elements.
    ``<apply>``
        Used to indicate the tree structure of the equation. These get
        translated but don't have a Myokit counterpart.

    Functions

    ``<power>``
        Becomes a :class:`myokit.Power`.
    ``<root>`` (with ``<degree>``)
        Becomes a :class:`myokit.Sqrt`.
    ``<exp>``
        Becomes a :class:`myokit.Exp`.
    ``<ln>``
        Becomes a :class:`myokit.Log`.
    ``<log>`` (with ``<logbase>``)
        Becomes a :class:`myokit.Log10` or a :class:`myokit.Log`.
    ``<abs>``
        Becomes a :class:`myokit.Abs`.
    ``<floor>``
        Becomes a :class:`myokit.Floor`.
    ``<ceiling>``
        Becomes a :class:`myokit.Ceil`.
    ``<quotient>``
        Becomes a :class:`myokit.Quotient`.
    ``<rem>``
        Becomes a :class:`myokit.Remainder`.

    Trigonometry

    ``<sin>``, ``<cos>`` and ``<tan>``
        Become :class:`myokit.Sin`, :class:`myokit.Cos` and
        :class:`myokit.Tan`.
    ``<arcsin>``, ``<arccos>`` and ``<arctan>``
        Become :class:`myokit.ASin`, :class:`myokit.ACos` and
        :class:`myokit.ATan`.
    ``<csc>``, ``<sec>`` and ``<cot>``
        Become ``1/sin``, ``1/cos`` and ``1/tan``.
    ``<arccsc>``, ``<arcsec>`` and ``<arccot>``
        Become ``asin(1/x)``, ``acos(1/x)`` and ``atan(1/x)``.

    Hyperbolic trigonometry

    ``<sinh>``
        Becomes ``0.5 * (exp(x) - exp(-x))``.
    ``<cosh>``
        Becomes ``0.5 * (exp(x) + exp(-x))``.
    ``<tanh>``
        Becomes ``(exp(2 * x) - 1) / (exp(2 * x) + 1)``.
    ``<arcsinh>``
        Becomes ``log(x + sqrt(1 + x*x))``.
    ``<arccosh>``
        Becomes ``log(x + sqrt(x + 1) * sqrt(x - 1))``.
    ``<arctanh>``
        Becomes ``0.5 * (log(1 + x) - log(1 - x))``.
    ``<csch>``
        Becomes ``2 / (exp(x) - exp(-x))``.
    ``<sech>``
        Becomes ``2 / (exp(x) + exp(-x))``.
    ``<coth>``
        Becomes ``(exp(2 * x) + 1) / (exp(2 * x) - 1)``.
    ``<arccsch>``
        Becomes ``log(sqrt(1 + 1 / x^2) + 1 / x)``.
    ``<arcsech>``
        Becomes ``log(sqrt(1 / x - 1) * sqrt(1 / x + 1) + 1 / x)``
    ``<arccoth>``
        Becomes ``0.5 * (log(1 + 1/x) - log(1 - 1/x))``.

    Logic and relations

    ``<piecewise>``, ``<piece>`` and ``<otherwise>``
        Becomes a :class:`myokit.Piecewise`.
    ``<and>``, ``<or>`` and ``<not>``
        Become :class:`myokit.And`, :class:`myokit.Or` and :class:`myokit.Not`.
    ``<xor>``
        Becomes ``(x or y) and not(x and y)``
    ``<eq>`` and ``<neq>``
        Becomes :class:`myokit.Equal` and :class:`NotEqual`.
    ``<lt>`` and ``<gt>``
        Become :class:`myokit.Less` and :class:`myokit.More`.
    ``<leq>`` and ``<geq>``
        Become :class:`myokit.LessEqual` and :class:`myokit.MoreEqual`.

    Constants

    ``<pi>``
        Becomes ``3.14159265358979323846``
    ``<exponentiale>``
        Becomes ``exp(1)``
    ``<true>``
        Becomes ``1``
    ``<false>``
        Becomes ``0``

    There are a few elements supported by CellML, but not by Myokit.

    ``<semantics>``, ``<annotation>`` and ``<annotation-xml>``
        These are not present in any electrophysiology model in the database.
    ``<notanumber>`` and ``<infinity>``
        These have no place in an ODE.
    ``<factorial>``
        There is no cardiac electrophysiology model in the database that uses
        these. Plus, factorials require the idea of integers (Myokit only has
        Reals) and only factorial(x) for x in [0,1,2,...,12] can be
        calculated without integer overflows.

    Finally, Myokit, but not CellML, supports quotients and remainders.

    """
    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)

    # Remove math node, if given
    if node.tagName == 'math':
        node = dom_child(node)

    # TODO: Check xmlns?

    # Return
    return parsex(node)
コード例 #10
0
    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)
コード例 #11
0
ファイル: _importer.py プロジェクト: teosbpl/myokit
 def model(self, path):
     # Parse xml file
     path = os.path.abspath(os.path.expanduser(path))
     self.log('Reading ' + str(path))
     dom = xml.dom.minidom.parse(path)
     xmodel = dom.getElementsByTagName('model')[0]
     # Get model node
     if xmodel.getAttribute('name'):
         name = str(xmodel.getAttribute('name'))
     elif xmodel.getAttribute('id'):
         name = str(xmodel.getAttribute('id'))
     else:
         name = 'Imported SBML model'
     # Create myokit model
     model = myokit.Model(self._convert_name(name))
     self.log('Reading model "' + model.meta['name'] + '"')
     # Create one giant component to hold all variables
     comp = model.add_component('sbml')
     # Handle notes, if given
     x = dom_child(xmodel, 'notes')
     if x:
         self.log('Converting <model> notes to ascii')
         model.meta['desc'] = html2ascii(x.toxml(), width=75)
         # width = 79 - 4 for tab!
     # Warn about missing functionality
     x = dom_child(xmodel, 'listOfCompartments')
     if x:
         self.warn('Compartments are not supported.')
     x = dom_child(xmodel, 'listOfSpecies')
     if x:
         self.warn('Species are not supported.')
     x = dom_child(xmodel, 'listOfConstraints')
     if x:
         self.warn('Constraints are not supported.')
     x = dom_child(xmodel, 'listOfReactions')
     if x:
         self.warn('Reactions are not supported.')
     x = dom_child(xmodel, 'listOfEvents')
     if x:
         self.warn('Events are not supported.')
     # Handle custom functions TODO???
     x = dom_child(xmodel, 'listOfFunctionDefinitions')
     if x:
         self.warn('Custom math functions are not (yet) implemented.')
     # Parse custom units
     x = dom_child(xmodel, 'listOfUnitDefinitions')
     if x:
         self._parse_units(model, comp, x)
     # Parse parameters (constants + parameters)
     x = dom_child(xmodel, 'listOfParameters')
     if x:
         self._parse_parameters(model, comp, x)
     # Parse rules (equations)
     x = dom_child(xmodel, 'listOfRules')
     if x:
         self._parse_rules(model, comp, x)
     # Parse extra initial assignments
     x = dom_child(xmodel, 'listOfInitialAssignments')
     if x:
         self._parse_initial_assignments(model, comp, x)
     # Write warnings to log
     self.log_warnings()
     # Run model validation, order variables etc
     try:
         model.validate()
     except myokit.IntegrityError as e:
         self.log_line()
         self.log('WARNING: Integrity error found in model:')
         self.log(e.message)
         self.log_line()
     # Return finished model
     return model
コード例 #12
0
    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
コード例 #13
0
    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