From 8479aa27486f408c7e0d9099d966d79b05ffd205 Mon Sep 17 00:00:00 2001
From: Stephan Seitz <stephan.seitz@fau.de>
Date: Sun, 18 Aug 2019 11:23:50 +0200
Subject: [PATCH 1/4] Let iterator of AssignmentCollection iterate over
 all_assignments (not only main_assignments)

---
 pystencils/simp/assignment_collection.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pystencils/simp/assignment_collection.py b/pystencils/simp/assignment_collection.py
index fa437a5d0..1e9a4c47e 100644
--- a/pystencils/simp/assignment_collection.py
+++ b/pystencils/simp/assignment_collection.py
@@ -347,7 +347,7 @@ class AssignmentCollection:
         return result
 
     def __iter__(self):
-        return self.main_assignments.__iter__()
+        return self.all_assignments.__iter__()
 
     @property
     def main_assignments_dict(self):
-- 
GitLab


From da5cfa52ef579ffac74ba635bc04a34d8365d6e6 Mon Sep 17 00:00:00 2001
From: Stephan Seitz <stephan.seitz@fau.de>
Date: Mon, 23 Sep 2019 09:20:44 +0200
Subject: [PATCH 2/4] Add dtype assumptions to cast_func

---
 pystencils/data_types.py | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/pystencils/data_types.py b/pystencils/data_types.py
index 86ce1747d..faa886718 100644
--- a/pystencils/data_types.py
+++ b/pystencils/data_types.py
@@ -82,6 +82,37 @@ class cast_func(sp.Function):
     def dtype(self):
         return self.args[1]
 
+    @property
+    def is_integer(self):
+        if hasattr(self.dtype, 'numpy_dtype'):
+            return np.issubdtype(self.dtype.numpy_dtype, np.integer) or super().is_integer
+        else:
+            return super().is_integer
+
+    @property
+    def is_negative(self):
+        if hasattr(self.dtype, 'numpy_dtype'):
+            if np.issubdtype(self.dtype.numpy_dtype, np.unsignedinteger):
+                return False
+
+        return super().is_negative
+
+    @property
+    def is_nonnegative(self):
+        if self.is_negative is False:
+            return True
+        else:
+            return super().is_nonnegative
+
+    @property
+    def is_real(self):
+        if hasattr(self.dtype, 'numpy_dtype'):
+            return np.issubdtype(self.dtype.numpy_dtype, np.integer) or \
+                np.issubdtype(self.dtype.numpy_dtype, np.floating) or \
+                super().is_real
+        else:
+            return super().is_real
+
 
 # noinspection PyPep8Naming
 class boolean_cast_func(cast_func, Boolean):
-- 
GitLab


From c3a4fb73647d84b02a4d4942f2a5b883d574cf8e Mon Sep 17 00:00:00 2001
From: Stephan Seitz <stephan.seitz@fau.de>
Date: Mon, 23 Sep 2019 09:23:57 +0200
Subject: [PATCH 3/4] Add property is_integer -> True to
 IntegerFunctionTwoArgsMixIn

---
 pystencils/integer_functions.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/pystencils/integer_functions.py b/pystencils/integer_functions.py
index b54bbaab2..fa5a4d739 100644
--- a/pystencils/integer_functions.py
+++ b/pystencils/integer_functions.py
@@ -25,6 +25,10 @@ class IntegerFunctionTwoArgsMixIn(sp.Function):
                 raise ValueError("Integer functions can only be constructed with typed expressions")
         return super().__new__(cls, *args)
 
+    @property
+    def is_integer(self):
+        return True
+
 
 # noinspection PyPep8Naming
 class bitwise_xor(IntegerFunctionTwoArgsMixIn):
-- 
GitLab


From 8d6100541dddc5b0bbe5fc40361fd99566d0c13f Mon Sep 17 00:00:00 2001
From: Stephan Seitz <stephan.seitz@fau.de>
Date: Mon, 23 Sep 2019 09:28:04 +0200
Subject: [PATCH 4/4] Add pystencils.math_optimizations

---
 pystencils/astnodes.py                       |  7 +++
 pystencils/math_optimizations.py             | 46 +++++++++++++++
 pystencils_tests/test_sympy_optimizations.py | 60 ++++++++++++++++++++
 3 files changed, 113 insertions(+)
 create mode 100644 pystencils/math_optimizations.py
 create mode 100644 pystencils_tests/test_sympy_optimizations.py

diff --git a/pystencils/astnodes.py b/pystencils/astnodes.py
index 393396aa2..b24138286 100644
--- a/pystencils/astnodes.py
+++ b/pystencils/astnodes.py
@@ -509,6 +509,13 @@ class SympyAssignment(Node):
         self.lhs = fast_subs(self.lhs, subs_dict)
         self.rhs = fast_subs(self.rhs, subs_dict)
 
+    def optimize(self, optimizations):
+        try:
+            from sympy.codegen.rewriting import optimize
+            self.rhs = optimize(self.rhs, optimizations)
+        except Exception:
+            pass
+
     @property
     def args(self):
         return [self._lhs_symbol, self.rhs]
diff --git a/pystencils/math_optimizations.py b/pystencils/math_optimizations.py
new file mode 100644
index 000000000..ad0114782
--- /dev/null
+++ b/pystencils/math_optimizations.py
@@ -0,0 +1,46 @@
+"""
+Default Sympy optimizations applied in pystencils kernels using :func:`sympy.codegen.rewriting.optimize`.
+
+See :func:`sympy.codegen.rewriting.optimize`.
+"""
+
+
+import itertools
+
+from pystencils import Assignment
+from pystencils.astnodes import SympyAssignment
+
+try:
+    from sympy.codegen.rewriting import optims_c99, optimize
+    from sympy.codegen.rewriting import ReplaceOptim
+    HAS_REWRITING = True
+
+    # Evaluates all constant terms
+    evaluate_constant_terms = ReplaceOptim(
+        lambda e: hasattr(e, 'is_constant') and e.is_constant and not e.is_integer,
+        lambda p: p.evalf()
+    )
+
+    optims_pystencils_cpu = [evaluate_constant_terms] + list(optims_c99)
+    optims_pystencils_gpu = [evaluate_constant_terms] + list(optims_c99)
+except ImportError:
+    from warnings import warn
+    warn("Could not import ReplaceOptim, optims_c99, optimize from sympy.codegen.rewriting."
+         "Please update your sympy installation!")
+    optims_c99 = []
+    optims_pystencils_cpu = []
+    optims_pystencils_gpu = []
+    HAS_REWRITING = False
+
+
+def optimize_assignments(assignments, optimizations):
+
+    if HAS_REWRITING:
+        assignments = [Assignment(a.lhs, optimize(a.rhs, optimizations))
+                       if hasattr(a, 'lhs')
+                       else a for a in assignments]
+        assignments_nodes = [a.atoms(SympyAssignment) for a in assignments]
+        for a in itertools.chain.from_iterable(assignments_nodes):
+            a.optimize(optimizations)
+
+    return assignments
diff --git a/pystencils_tests/test_sympy_optimizations.py b/pystencils_tests/test_sympy_optimizations.py
new file mode 100644
index 000000000..745b936ea
--- /dev/null
+++ b/pystencils_tests/test_sympy_optimizations.py
@@ -0,0 +1,60 @@
+import pytest
+import sympy as sp
+
+import pystencils
+from pystencils.math_optimizations import HAS_REWRITING, optimize_assignments, optims_pystencils_cpu
+
+
+@pytest.mark.skipif(not HAS_REWRITING, reason="need sympy.codegen.rewriting")
+def test_sympy_optimizations():
+    for target in ('cpu', 'gpu'):
+        x, y, z = pystencils.fields('x, y, z:  float32[2d]')
+
+        # Triggers Sympy's expm1 optimization
+        assignments = pystencils.AssignmentCollection({
+            x[0, 0]: sp.exp(y[0, 0]) - 1
+        })
+
+        assignments = optimize_assignments(assignments, optims_pystencils_cpu)
+
+        ast = pystencils.create_kernel(assignments, target=target)
+        code = str(pystencils.show_code(ast))
+        assert 'expm1(' in code
+
+
+@pytest.mark.skipif(not HAS_REWRITING, reason="need sympy.codegen.rewriting")
+def test_evaluate_constant_terms():
+    for target in ('cpu', 'gpu'):
+        x, y, z = pystencils.fields('x, y, z:  float32[2d]')
+
+        # Triggers Sympy's expm1 optimization
+        assignments = pystencils.AssignmentCollection({
+            x[0, 0]: -sp.cos(1) + y[0, 0]
+        })
+
+        assignments = optimize_assignments(assignments, optims_pystencils_cpu)
+
+        ast = pystencils.create_kernel(assignments, target=target)
+        code = str(pystencils.show_code(ast))
+        assert 'cos(' not in code
+        print(code)
+
+
+@pytest.mark.skipif(not HAS_REWRITING, reason="need sympy.codegen.rewriting")
+def test_do_not_evaluate_constant_terms():
+    optimizations = pystencils.math_optimizations.optims_pystencils_cpu
+    optimizations.remove(pystencils.math_optimizations.evaluate_constant_terms)
+
+    for target in ('cpu', 'gpu'):
+        x, y, z = pystencils.fields('x, y, z:  float32[2d]')
+
+        assignments = pystencils.AssignmentCollection({
+            x[0, 0]: -sp.cos(1) + y[0, 0]
+        })
+
+        optimize_assignments(assignments, optimizations)
+
+        ast = pystencils.create_kernel(assignments, target=target)
+        code = str(pystencils.show_code(ast))
+        assert 'cos(' in code
+        print(code)
-- 
GitLab