From 68e743d936434012c19953077ec8e5b6932c5c06 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 21 May 2018 17:39:46 -0500 Subject: [PATCH 1/6] Implement TeX mapper --- pymbolic/mapper/stringifier.py | 124 +++++++++++++++++++++++++++++++++ test/test_pymbolic.py | 68 ++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/pymbolic/mapper/stringifier.py b/pymbolic/mapper/stringifier.py index 44057df..9783ad9 100644 --- a/pymbolic/mapper/stringifier.py +++ b/pymbolic/mapper/stringifier.py @@ -562,4 +562,128 @@ class SimplifyingSortingStringifyMapper(StringifyMapper): # }}} + +# {{{ tex stringifier + +class TeXMapper(StringifyMapper): + + COMPARISON_OP_TO_LATEX = { + "==": r"=", + "!=": r"\ne", + "<=": r"\le", + ">=": r"\ge", + "<": r"<", + ">": r">", + } + + def map_remainder(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.format(r"%s \bmod %s", + self.rec(expr.numerator, PREC_PRODUCT, *args, **kwargs), + self.rec(expr.denominator, PREC_POWER, *args, **kwargs)), + enclosing_prec, PREC_PRODUCT+1) + + def map_left_shift(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.format(r"%s \ll %s", + self.rec(expr.shiftee, PREC_SHIFT+1, *args, **kwargs), + self.rec(expr.shift, PREC_SHIFT+1, *args, **kwargs)), + enclosing_prec, PREC_SHIFT) + + def map_right_shift(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.format(r"%s \gg %s", + self.rec(expr.shiftee, PREC_SHIFT+1, *args, **kwargs), + self.rec(expr.shift, PREC_SHIFT+1, *args, **kwargs)), + enclosing_prec, PREC_SHIFT) + + def map_bitwise_xor(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.join_rec( + r" \wedge ", expr.children, PREC_BITWISE_XOR, *args, **kwargs), + enclosing_prec, PREC_BITWISE_XOR) + + def map_product(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.join_rec(" ", expr.children, PREC_PRODUCT, *args, **kwargs), + enclosing_prec, PREC_PRODUCT) + + def map_power(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.format("{%s}^{%s}", + self.rec(expr.base, PREC_NONE, *args, **kwargs), + self.rec(expr.exponent, PREC_NONE, *args, **kwargs)), + enclosing_prec, PREC_NONE) + + def map_min(self, expr, enclosing_prec, *args, **kwargs): + from pytools import is_single_valued + if is_single_valued(expr.children): + return self.rec(expr.children[0], enclosing_prec) + + what = type(expr).__name__.lower() + return self.format(r"\%s(%s)", + what, self.join_rec(", ", expr.children, PREC_NONE, *args, **kwargs)) + + def map_max(self, expr, enclosing_prec): + return self.map_min(expr, enclosing_prec) + + def map_floor_div(self, expr, enclosing_prec, *args, **kwargs): + return self.format(r"\lfloor {%s} / {%s} \rfloor", + self.rec(expr.numerator, PREC_NONE, *args, **kwargs), + self.rec(expr.denominator, PREC_NONE, *args, **kwargs)) + + def map_subscript(self, expr, enclosing_prec, *args, **kwargs): + if isinstance(expr.index, tuple): + index_str = self.join_rec(", ", expr.index, PREC_NONE, *args, **kwargs) + else: + index_str = self.rec(expr.index, PREC_NONE, *args, **kwargs) + + return self.format("{%s}_{%s}", + self.rec(expr.aggregate, PREC_CALL, *args, **kwargs), + index_str) + + def map_logical_not(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + r"\neg " + self.rec(expr.child, PREC_UNARY, *args, **kwargs), + enclosing_prec, PREC_UNARY) + + def map_logical_or(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.join_rec( + r" \vee ", expr.children, PREC_LOGICAL_OR, *args, **kwargs), + enclosing_prec, PREC_LOGICAL_OR) + + def map_logical_and(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.join_rec( + r" \wedge ", expr.children, PREC_LOGICAL_AND, *args, **kwargs), + enclosing_prec, PREC_LOGICAL_AND) + + def map_comparison(self, expr, enclosing_prec, *args, **kwargs): + return self.parenthesize_if_needed( + self.format("%s %s %s", + self.rec(expr.left, PREC_COMPARISON, *args, **kwargs), + self.COMPARISON_OP_TO_LATEX[expr.operator], + self.rec(expr.right, PREC_COMPARISON, *args, **kwargs)), + enclosing_prec, PREC_COMPARISON) + + def map_substitution(self, expr, enclosing_prec, *args, **kwargs): + substs = ", ".join( + "%s=%s" % (name, self.rec(val, PREC_NONE, *args, **kwargs)) + for name, val in zip(expr.variables, expr.values)) + + return self.format("[%s]\{%s\}", + self.rec(expr.child, PREC_NONE, *args, **kwargs), + substs) + + def map_derivative(self, expr, enclosing_prec, *args, **kwargs): + derivs = " ".join( + r"\frac{\partial}{\partial %s}" % v + for v in expr.variables) + + return self.format("%s %s", + derivs, self.rec(expr.child, PREC_PRODUCT, *args, **kwargs)) + +# }}} + # vim: fdm=marker diff --git a/test/test_pymbolic.py b/test/test_pymbolic.py index f6b8733..0518a0a 100644 --- a/test/test_pymbolic.py +++ b/test/test_pymbolic.py @@ -492,6 +492,74 @@ def test_stringifier_preserve_shift_order(): assert parse(str(expr)) == expr +TEX_TEMPLATE = r"""\documentclass{article} +\usepackage{amsmath} + +\begin{document} +%s +\end{document}""" + + +def test_tex_mapper(): + from pymbolic import parse + from pymbolic.mapper.stringifier import TeXMapper, StringifyMapper + + tm = TeXMapper() + sm = StringifyMapper() + + equations = [] + + def add(expr): + # Add an equation to the list of tests. + equations.append("\[%s\] %% from: %s" % (tm(expr), sm(expr))) + + add(parse("a * b + c")) + add(parse("f(a,b,c)")) + add(parse("a ** b ** c")) + add(parse("(a | b) ^ ~c")) + add(parse("a << b")) + add(parse("a >> b")) + add(parse("a[i,j,k]")) + add(parse("a[1:3]")) + add(parse("a // b")) + add(parse("not (a or b) and c")) + add(parse("(a % b) % c")) + add(parse("(a >= b) or (b <= c)")) + add(prim.Min((1,)) + prim.Max((1, 2))) + add(prim.Substitution(prim.Variable("x") ** 2, ("x",), (2,))) + add(prim.Derivative(parse("x**2"), ("x",))) + + # Run LaTeX and ensure the file compiles. + import os + import tempfile + import subprocess + import shutil + + latex_dir = tempfile.mkdtemp("pymbolic") + + try: + tex_file_path = os.path.join(latex_dir, "input.tex") + + with open(tex_file_path, "w") as tex_file: + contents = TEX_TEMPLATE % "\n".join(equations) + tex_file.write(contents) + + try: + subprocess.check_output( + ["latex", + "-interaction=nonstopmode", + "-output-directory=%s" % latex_dir, + tex_file_path], + universal_newlines=True) + except FileNotFoundError: + pytest.skip("latex command not found") + except subprocess.CalledProcessError as err: + assert False, str(err.output) + + finally: + shutil.rmtree(latex_dir) + + if __name__ == "__main__": import sys if len(sys.argv) > 1: -- GitLab From 33129fadd0daf0aaea3acc8799093fefc1167532 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 21 May 2018 17:48:33 -0500 Subject: [PATCH 2/6] rename: TeXMapper -> LaTeXMapper --- pymbolic/mapper/stringifier.py | 4 ++-- test/test_pymbolic.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pymbolic/mapper/stringifier.py b/pymbolic/mapper/stringifier.py index 9783ad9..0895abf 100644 --- a/pymbolic/mapper/stringifier.py +++ b/pymbolic/mapper/stringifier.py @@ -563,9 +563,9 @@ class SimplifyingSortingStringifyMapper(StringifyMapper): # }}} -# {{{ tex stringifier +# {{{ latex stringifier -class TeXMapper(StringifyMapper): +class LaTeXMapper(StringifyMapper): COMPARISON_OP_TO_LATEX = { "==": r"=", diff --git a/test/test_pymbolic.py b/test/test_pymbolic.py index 0518a0a..d2a27c3 100644 --- a/test/test_pymbolic.py +++ b/test/test_pymbolic.py @@ -492,7 +492,7 @@ def test_stringifier_preserve_shift_order(): assert parse(str(expr)) == expr -TEX_TEMPLATE = r"""\documentclass{article} +LATEX_TEMPLATE = r"""\documentclass{article} \usepackage{amsmath} \begin{document} @@ -500,11 +500,11 @@ TEX_TEMPLATE = r"""\documentclass{article} \end{document}""" -def test_tex_mapper(): +def test_latex_mapper(): from pymbolic import parse - from pymbolic.mapper.stringifier import TeXMapper, StringifyMapper + from pymbolic.mapper.stringifier import LaTeXMapper, StringifyMapper - tm = TeXMapper() + tm = LaTeXMapper() sm = StringifyMapper() equations = [] -- GitLab From 215f54e7403cfc5ee90734f80749934c94eb0056 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 21 May 2018 17:51:20 -0500 Subject: [PATCH 3/6] Properly rename variable --- test/test_pymbolic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_pymbolic.py b/test/test_pymbolic.py index d2a27c3..bb92ac8 100644 --- a/test/test_pymbolic.py +++ b/test/test_pymbolic.py @@ -541,7 +541,7 @@ def test_latex_mapper(): tex_file_path = os.path.join(latex_dir, "input.tex") with open(tex_file_path, "w") as tex_file: - contents = TEX_TEMPLATE % "\n".join(equations) + contents = LATEX_TEMPLATE % "\n".join(equations) tex_file.write(contents) try: -- GitLab From dc9083d6f0b000d92389863a85f3bf23dc3aa355 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 21 May 2018 17:56:33 -0500 Subject: [PATCH 4/6] LaTeXMapper.map_remainder: same parenthesizing behavior as parent class --- pymbolic/mapper/stringifier.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pymbolic/mapper/stringifier.py b/pymbolic/mapper/stringifier.py index 0895abf..c2bc9a2 100644 --- a/pymbolic/mapper/stringifier.py +++ b/pymbolic/mapper/stringifier.py @@ -577,11 +577,10 @@ class LaTeXMapper(StringifyMapper): } def map_remainder(self, expr, enclosing_prec, *args, **kwargs): - return self.parenthesize_if_needed( - self.format(r"%s \bmod %s", - self.rec(expr.numerator, PREC_PRODUCT, *args, **kwargs), - self.rec(expr.denominator, PREC_POWER, *args, **kwargs)), - enclosing_prec, PREC_PRODUCT+1) + return self.format(r"(%s \bmod %s)", + self.rec(expr.numerator, PREC_PRODUCT, *args, **kwargs), + self.rec(expr.denominator, PREC_POWER, *args, **kwargs)), + def map_left_shift(self, expr, enclosing_prec, *args, **kwargs): return self.parenthesize_if_needed( -- GitLab From 8511eacc0d8383214d623f6859624c47f0d61989 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 21 May 2018 18:02:39 -0500 Subject: [PATCH 5/6] flake8 fix --- pymbolic/mapper/stringifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pymbolic/mapper/stringifier.py b/pymbolic/mapper/stringifier.py index c2bc9a2..8c9453b 100644 --- a/pymbolic/mapper/stringifier.py +++ b/pymbolic/mapper/stringifier.py @@ -581,7 +581,6 @@ class LaTeXMapper(StringifyMapper): self.rec(expr.numerator, PREC_PRODUCT, *args, **kwargs), self.rec(expr.denominator, PREC_POWER, *args, **kwargs)), - def map_left_shift(self, expr, enclosing_prec, *args, **kwargs): return self.parenthesize_if_needed( self.format(r"%s \ll %s", -- GitLab From accbc45c3122daede9d91b573b9bb0b4aaac87b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Tue, 22 May 2018 06:16:57 -0400 Subject: [PATCH 6/6] Add LaTeXMapper to docs --- doc/mappers.rst | 9 --------- pymbolic/mapper/stringifier.py | 11 +++++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/mappers.rst b/doc/mappers.rst index ce56ea9..a3c60b6 100644 --- a/doc/mappers.rst +++ b/doc/mappers.rst @@ -12,15 +12,6 @@ Converting to strings and code .. automodule:: pymbolic.mapper.stringifier -Mappers -******* - -.. autoclass:: StringifyMapper - - .. automethod:: __call__ - -.. autoclass:: CSESplittingStringifyMapperMixin - .. automodule:: pymbolic.mapper.c_code .. autoclass:: CCodeMapper diff --git a/pymbolic/mapper/stringifier.py b/pymbolic/mapper/stringifier.py index 8c9453b..26c2932 100644 --- a/pymbolic/mapper/stringifier.py +++ b/pymbolic/mapper/stringifier.py @@ -43,6 +43,17 @@ Precedence constants .. data:: PREC_LOGICAL_AND .. data:: PREC_LOGICAL_OR .. data:: PREC_NONE + +Mappers +******* + +.. autoclass:: StringifyMapper + + .. automethod:: __call__ + +.. autoclass:: CSESplittingStringifyMapperMixin + +.. autoclass:: LaTeXMapper """ -- GitLab