# Copyright (C) 2013 Johan Hake
#
# This file is part of Gotran.
#
# Gotran is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gotran is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Gotran. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["MathMLBaseParser", "MathMLCPPParser"]
from gotran.common import error
from modelparameters.codegeneration import _all_keywords
[docs]class MathMLBaseParser(object):
def __init__(self, use_sympy_integers=False):
self._state_variable = None
self._derivative = None
self.variables_names = set()
self.use_sympy_integers = use_sympy_integers
self._precedence = {
"piecewise" : 0,
"power" : 0,
"rem" : 0.5,
"divide": 1,
"times" : 2,
"minus" : 4,
"plus" : 5,
"lt" : 6,
"gt" : 6,
"leq" : 6,
"geq" : 6,
"eq" : 10,
"exp" : 10,
"ln" : 10,
"abs" : 10,
"floor" : 10,
"log" : 10,
"root" : 10,
"tan" : 10,
"cos" : 10,
"sin" : 10,
"tanh" : 10,
"cosh" : 10,
"sinh" : 10,
"arccos": 10,
"arcsin": 10,
"arctan": 10,
}
self._operators = {
"power" : '**',
"rem" : ' % ',
"divide": '/',
"times" : '*',
"minus" : ' - ',
"plus" : ' + ',
"lt" : ' < ',
"gt" : ' > ',
"leq" : ' <= ',
"geq" : ' >= ',
"eq" : ' = ',
"exp" : 'exp',
"ln" : 'log',
"abs" : 'abs',
"floor" : 'floor',
"log" : 'log',
"root" : 'sqrt',
"tan" : 'tan',
"cos" : 'cos',
"sin" : 'sin',
"tanh" : 'tanh',
"cosh" : 'cosh',
"sinh" : 'sinh',
"arccos": 'acos',
"arcsin": 'asin',
"arctan": 'atan',
}
[docs] def use_parenthesis(self, child, parent, first_operand=True):
"""
Return true if child operation need parenthesis
"""
if parent is None:
return False
parent_prec = self._precedence[parent]
if parent == "minus" and not first_operand:
parent_prec -= 0.5
if parent == "divide" and child == "times" and first_operand:
return False
if parent == "minus" and child == "plus" and first_operand:
return False
return parent_prec < self._precedence[child]
def __getitem__(self, operator):
return self._operators[operator]
def _gettag(self, node):
"""
Splits off the namespace part from name, and returns the rest, the tag
"""
return "".join(node.tag.split("}")[1:])
[docs] def parse(self, root):
"""
Recursively parse a mathML subtree and return an list of tokens
together with any state variable and derivative.
"""
self._state_variable = None
self._derivative = None
self.used_variables = set()
equation_list = self._parse_subtree(root)
return equation_list, self._state_variable, self._derivative, \
self.used_variables
def _parse_subtree(self, root, parent=None, first_operand=True):
op = self._gettag(root)
# If the tag i "apply" pick the operator and continue parsing
if op == "apply":
children = root.getchildren()
op = self._gettag(children[0])
root = children[1:]
# If use special method to parse
if hasattr(self, "_parse_" + op):
return getattr(self, "_parse_" + op)(root, parent)
elif op in list(self._operators.keys()):
# Build the equation string
eq = []
# Check if we need parenthesis
use_parent = self.use_parenthesis(op, parent, first_operand)
# If unary operator
if len(root) == 1:
# Special case if operator is "minus"
if op == "minus":
# If an unary minus is infront of a cn or ci we skip
# parenthesize
if self._gettag(root[0]) in ["ci", "cn"]:
use_parent = False
# If an unary minus is infront of a plus we always use parenthesize
if self._gettag(root[0]) == "apply" and \
self._gettag(root[0].getchildren()[0]) in ["plus", "minus"]:
use_parent = True
eq += ["-"]
else:
# Always use paranthesis for unary operators
use_parent = True
eq += [self._operators[op]]
eq += ["("]*use_parent + self._parse_subtree(root[0], op) + \
[")"]*use_parent
return eq
else:
# Binary operator
eq += ["("] * use_parent + self._parse_subtree(root[0], op)
for operand in root[1:]:
eq = eq + [self._operators[op]] + self._parse_subtree(\
operand, op, first_operand=False)
eq = eq + [")"]*use_parent
return eq
else:
error("No support for parsing MathML " + op + " operator.")
def _parse_conditional(self, condition, operands, parent):
return [condition] + ["("] + self._parse_subtree(operands[0], parent) \
+ [", "] + self._parse_subtree(operands[1], parent) + [")"]
def _parse_and(self, operands, parent):
ret = ["And("]
for operand in operands:
ret += self._parse_subtree(operand, parent) + [", "]
return ret + [")"]
def _parse_or(self, operands, parent):
ret = ["Or("]
for operand in operands:
ret += self._parse_subtree(operand, parent) + [", "]
return ret + [")"]
def _parse_lt(self, operands, parent):
return self._parse_conditional("Lt", operands, "lt")
def _parse_leq(self, operands, parent):
return self._parse_conditional("Le", operands, "leq")
def _parse_gt(self, operands, parent):
return self._parse_conditional("Gt", operands, "gt")
def _parse_geq(self, operands, parent):
return self._parse_conditional("Ge", operands, "geq")
def _parse_neq(self, operands, parent):
return self._parse_conditional("Ne", operands, "neq")
def _parse_eq(self, operands, parent):
# Parsing conditional
if parent == "piecewise":
return self._parse_conditional("Eq", operands, "eq")
# Parsing assignment
return self._parse_subtree(operands[0], "eq") + [self["eq"]] + \
self._parse_subtree(operands[1], "eq")
def _parse_pi(self, var, parent):
return ["pi"]
def _parse_ci(self, var, parent):
varname = var.text.strip()
if varname in _all_keywords:
varname = varname + "_"
self.used_variables.add(varname)
return [varname]
def _parse_cn(self, var, parent):
value = var.text.strip()
if "type" in list(var.keys()) and var.get("type") == "e-notation":
# Get rid of potential float repr
exponent = "e" + str(int(var.getchildren()[0].tail.strip()))
else:
exponent = ""
value += exponent
if self.use_sympy_integers:
# Fix possible strangeness with integer division in Python...
nums = [1.0, 2.0, 3.0, 4.0, 5.0, 10.0]
num_strs = ["one", "two", "three", "four", "five", "ten"]
if eval(value) in nums:
value = dict(t for t in zip(nums, num_strs))[eval(value)]
#elif "." not in value and "e" not in value:
# value += ".0"
return [value]
def _parse_diff(self, operands, parent):
# Store old used_variables so we can erase any collected state
# variables
used_variables_prior_parse = self.used_variables.copy()
x = "".join(self._parse_subtree(operands[1], "diff"))
y = "".join(self._parse_subtree(operands[0], "diff"))
if x in _all_keywords:
x = x + "_"
if y == "time":
y = "t"
d = "d" + x + "_d" + y
# Restore used_variables
self.used_variables = used_variables_prior_parse
# Store derivative
self.used_variables.add(d)
# This is an in/out variable remember it
self._derivative = d
self._state_variable = x
return [d]
def _parse_bvar(self, var, parent):
if len(var) == 1:
return self._parse_subtree(var[0], "bvar")
else:
error("ERROR: No support for higher order derivatives.")
def _parse_piecewise(self, cases, parent=None):
if len(cases) == 2:
piece_children = cases[0].getchildren()
cond = self._parse_subtree(piece_children[1], "piecewise")
true = self._parse_subtree(piece_children[0])
false = self._parse_subtree(cases[1].getchildren()[0])
return ["Conditional", "("] + cond + [", "] + true + [", "] + \
false + [")"]
else:
piece_children = cases[0].getchildren()
cond = self._parse_subtree(piece_children[1], "piecewise")
true = self._parse_subtree(piece_children[0])
return ["Conditional", "("] + cond + [", "] + true + [", "] + \
self._parse_piecewise(cases[1:]) + [")"]
[docs]class MathMLCPPParser(MathMLBaseParser):
def _parse_power(self, operands):
return ["pow", "("] + self._parse_subtree(operands[0]) + [", "] + \
self._parse_subtree(operands[1]) + [")"]
def _parse_piecewise(self, cases):
if len(cases) == 2:
piece_children = cases[0].getchildren()
cond = self._parse_subtree(piece_children[1])
true = self._parse_subtree(piece_children[0])
false = self._parse_subtree(cases[1].getchildren()[0])
return ["("] + cond + ["?"] + true + [":"] + false + [")"]
else:
sys.exit("ERROR: No support for cases with other than two "\
"possibilities.")