diff --git a/pymbolic/interop/common.py b/pymbolic/interop/common.py index dd9177537bc95d09615c04022129c1d9066f908b..d8b75f50e9c7915af98f90ff05419efd7afa9a91 100644 --- a/pymbolic/interop/common.py +++ b/pymbolic/interop/common.py @@ -111,7 +111,10 @@ class SympyLikeToPymbolicMapper(SympyLikeMapperBase): return prim.CommonSubexpression( self.rec(expr.args[0]), expr.prefix) - def not_supported(self, expr): + def map_UnevaluatedExpr(self, expr): # noqa + return self.rec(expr.args[0]) + + def not_supported(self, expr): # noqa if isinstance(expr, int): return expr elif getattr(expr, "is_Function", False): diff --git a/pymbolic/interop/maxima.py b/pymbolic/interop/maxima.py index 2dabf2bcc3fdb32373d87c1adea5d22ec5b94b7f..e2c624c5f093188e8103ead233d074e090c46c32 100644 --- a/pymbolic/interop/maxima.py +++ b/pymbolic/interop/maxima.py @@ -40,6 +40,7 @@ import six from six.moves import intern import re import pytools +import numpy as np from pymbolic.mapper.stringifier import StringifyMapper from pymbolic.parser import Parser as ParserBase, FinalizedTuple @@ -92,10 +93,12 @@ class MaximaStringifyMapper(StringifyMapper): class MaximaParser(ParserBase): power_sym = intern("power") imag_unit = intern("imag_unit") + euler_number = intern("euler_number") lex_table = [ (power_sym, pytools.lex.RE(r"\^")), (imag_unit, pytools.lex.RE(r"%i")), + (euler_number, pytools.lex.RE(r"%e")), ] + ParserBase.lex_table def parse_prefix(self, pstate): @@ -126,6 +129,9 @@ class MaximaParser(ParserBase): elif next_tag is self.imag_unit: pstate.advance() return 1j + elif next_tag is self.euler_number: + pstate.advance() + return np.e elif next_tag is p._identifier: if six.PY3: return primitives.Variable(pstate.next_str_and_advance()) @@ -158,7 +164,6 @@ class MaximaParser(ParserBase): pstate.advance() if left_exp == primitives.Variable("matrix"): - import numpy as np left_exp = np.array(list(list(row) for row in args)) else: left_exp = primitives.Call(left_exp, args) @@ -197,7 +202,12 @@ class MaximaParser(ParserBase): did_something = True elif next_tag is self.power_sym and p._PREC_POWER > min_precedence: pstate.advance() - left_exp **= self.parse_expression(pstate, p._PREC_POWER) + exponent = self.parse_expression(pstate, p._PREC_POWER) + if left_exp == np.e: + from pymbolic.primitives import Call, Variable + left_exp = Call(Variable("exp"), (exponent,)) + else: + left_exp **= exponent did_something = True elif next_tag is p._comma and p._PREC_COMMA > min_precedence: # The precedence makes the comma left-associative. diff --git a/pymbolic/interop/sympy.py b/pymbolic/interop/sympy.py index b02d5e8b692040abb6e46cd8fecde95d4ad2d035..1e31eade77fdd4a0faa0730319d312031f3cd57d 100644 --- a/pymbolic/interop/sympy.py +++ b/pymbolic/interop/sympy.py @@ -28,6 +28,9 @@ THE SOFTWARE. from pymbolic.interop.common import ( SympyLikeToPymbolicMapper, PymbolicToSympyLikeMapper) +import pymbolic.primitives as prim +from functools import partial + import sympy @@ -60,6 +63,34 @@ class SympyToPymbolicMapper(SympyLikeToPymbolicMapper): def map_long(self, expr): return long(expr) # noqa + def map_Indexed(self, expr): # noqa + return prim.Subscript( + self.rec(expr.args[0].args[0]), + tuple(self.rec(i) for i in expr.args[1:]) + ) + + def map_Piecewise(self, expr): # noqa + # We only handle piecewises with 2 arguments! + assert len(expr.args) == 2 + # We only handle if/else cases + assert expr.args[1][1].is_Boolean and bool(expr.args[1][1]) is True + then = self.rec(expr.args[0][0]) + else_ = self.rec(expr.args[1][0]) + cond = self.rec(expr.args[0][1]) + return prim.If(cond, then, else_) + + def _comparison_operator(self, expr, operator=None): + left = self.rec(expr.args[0]) + right = self.rec(expr.args[1]) + return prim.Comparison(left, operator, right) + + map_Equality = partial(_comparison_operator, operator="==") + map_Unequality = partial(_comparison_operator, operator="!=") + map_GreaterThan = partial(_comparison_operator, operator=">=") + map_LessThan = partial(_comparison_operator, operator="<=") + map_StrictGreaterThan = partial(_comparison_operator, operator=">") + map_StrictLessThan = partial(_comparison_operator, operator="<") + # }}} @@ -77,6 +108,36 @@ class PymbolicToSympyMapper(PymbolicToSympyLikeMapper): return self.sym.Derivative(self.rec(expr.child), *[self.sym.Symbol(v) for v in expr.variables]) + def map_subscript(self, expr): + return self.sym.tensor.indexed.Indexed( + self.rec(expr.aggregate), + *tuple(self.rec(i) for i in expr.index_tuple) + ) + + def map_if(self, expr): + cond = self.rec(expr.condition) + return self.sym.Piecewise((self.rec(expr.then), cond), + (self.rec(expr.else_), True) + ) + + def map_comparison(self, expr): + left = self.rec(expr.left) + right = self.rec(expr.right) + if expr.operator == "==": + return self.sym.Equality(left, right) + elif expr.operator == "!=": + return self.sym.Unequality(left, right) + elif expr.operator == "<": + return self.sym.StrictLessThan(left, right) + elif expr.operator == ">": + return self.sym.StrictGreaterThan(left, right) + elif expr.operator == "<=": + return self.sym.LessThan(left, right) + elif expr.operator == ">=": + return self.sym.GreaterThan(left, right) + else: + raise NotImplementedError("Unknown operator '%s'" % expr.operator) + # }}} diff --git a/test/test_maxima.py b/test/test_maxima.py index 7ddbb687dd711b2ff83cadaf8d81c72c0fed8a42..7bd2ad6b640be092d6d3ef3ba21ae5f2bac33948 100644 --- a/test/test_maxima.py +++ b/test/test_maxima.py @@ -74,6 +74,7 @@ def test_strict_round_trip(knl): 2j, parse("x**y"), Quotient(1, 2), + parse("exp(x)") ] for expr in exprs: result = knl.eval_expr(expr) diff --git a/test/test_sympy.py b/test/test_sympy.py index 5415cb6227b82978dbc774d1a50075d8eca2cc6f..d248fe468e58b57e48b6b3a22837d7d4b99a55a1 100644 --- a/test/test_sympy.py +++ b/test/test_sympy.py @@ -25,7 +25,7 @@ THE SOFTWARE. import pytest import pymbolic.primitives as prim -x_, y_ = (prim.Variable(s) for s in "x y".split()) +x_, y_, i_, j_ = (prim.Variable(s) for s in "x y i j".split()) # {{{ to pymbolic test @@ -51,6 +51,11 @@ def _test_to_pymbolic(mapper, sym, use_symengine): assert mapper(sym.Function("f")(x)) == prim.Variable("f")(x_) assert mapper(sym.exp(x)) == prim.Variable("exp")(x_) + # indexed accesses + if not use_symengine: + i, j = sym.symbols("i,j") + assert mapper(sym.tensor.indexed.Indexed(x, i, j)) == x_[i_, j_] + # constants import math # FIXME: Why isn't this exact? @@ -91,7 +96,11 @@ def _test_from_pymbolic(mapper, sym, use_symengine): deriv = sym.Derivative(x**2, x) assert mapper(prim.Derivative(x_**2, ("x",))) == deriv - assert mapper(x_[0]) == sym.Symbol("x_0") + if use_symengine: + assert mapper(x_[0]) == sym.Symbol("x_0") + else: + i, j = sym.symbols("i,j") + assert mapper(x_[i_, j_]) == sym.tensor.indexed.Indexed(x, i, j) assert mapper(prim.Variable("f")(x_)) == sym.Function("f")(x) @@ -114,6 +123,17 @@ def test_pymbolic_to_sympy(): _test_from_pymbolic(mapper, sym, False) +def test_sympy_if_condition(): + pytest.importorskip("sympy") + from pymbolic.interop.sympy import PymbolicToSympyMapper, SympyToPymbolicMapper + forward = PymbolicToSympyMapper() + backward = SympyToPymbolicMapper() + + # Test round trip to sympy and back + expr = prim.If(prim.Comparison(x_, "<=", y_), 1, 0) + assert backward(forward(expr)) == expr + + if __name__ == "__main__": import sys if len(sys.argv) > 1: