From 236cd1c6715ed55039cb92e7d90d4d5e0c69d961 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Wed, 13 Mar 2024 16:16:54 +0100
Subject: [PATCH 01/33] Cherry-picked be7e45e5bb08baf12bb18edfafbfc6052062c5c0
 from old version: Draft for an updated version of the TensorialVectorSpace as
 a first step towards vector-vector operators.

---
 hog/function_space.py             | 94 ++++++++++++++++++++-----------
 hog/math_helpers.py               | 10 +++-
 hog_tests/test_function_spaces.py | 66 ++++++++++++++++++++++
 3 files changed, 133 insertions(+), 37 deletions(-)
 create mode 100644 hog_tests/test_function_spaces.py

diff --git a/hog/function_space.py b/hog/function_space.py
index 08fe799..aa70ef3 100644
--- a/hog/function_space.py
+++ b/hog/function_space.py
@@ -57,7 +57,7 @@ class FunctionSpace(Protocol):
         geometry: ElementGeometry,
         domain: str = "reference",
         dof_map: Optional[List[int]] = None,
-    ) -> List[sp.Expr]:
+    ) -> List[sp.MatrixBase]:
         """The basis functions of this FEM space."""
         ...
 
@@ -69,6 +69,17 @@ class FunctionSpace(Protocol):
     ) -> List[sp.MatrixBase]:
         """Returns a list containing the gradients of the shape functions on the element.
 
+        Particularly, for each (vector- or scalar-valued) shape function N = (N_1, ..., N_n) returns the _transposed_
+        Jacobian
+
+                          ⎡∂N₁/∂x₁  ···  ∂Nₙ/∂x₁⎤
+                          ⎢   ·             ·   ⎥
+        grad(N) = J(N)ᵀ = ⎢   ·             ·   ⎥
+                          ⎢   ·             ·   ⎥
+                          ⎣∂N₁/∂xₖ  ···  ∂Nₙ/∂xₖ ⎦
+
+        i.e., the returned gradient is a column vector for scalar shape functions.
+
         :param dof_map: this list can be used to specify (remap) the DoF ordering of the element
         """
         if domain in ["ref", "reference"]:
@@ -131,7 +142,7 @@ class LagrangianFunctionSpace(FunctionSpace):
         geometry: ElementGeometry,
         domain: str = "reference",
         dof_map: Optional[List[int]] = None,
-    ) -> List[sp.Expr]:
+    ) -> List[sp.MatrixBase]:
         """Returns a list containing the shape functions on the element.
 
         :param dof_map: this list can be used to specify (remap) the DoF ordering of the element
@@ -249,6 +260,7 @@ class LagrangianFunctionSpace(FunctionSpace):
             if dof_map:
                 raise HOGException("DoF reordering not implemented.")
 
+            basis_functions = [sp.Matrix([b]) for b in basis_functions]
             return basis_functions
 
         raise HOGException(f"Shape function not available for domain type {domain}")
@@ -266,9 +278,35 @@ class LagrangianFunctionSpace(FunctionSpace):
 
 
 class TensorialVectorFunctionSpace(FunctionSpace):
-    def __init__(self, component: int, function_space: FunctionSpace):
+    """
+    Given a scalar function space, this class can be used to construct a tensorial vector-function space.
+
+    Each shape function of the resulting tensorial space has only one non-zero component that is set to one of the
+    shape functions of the given scalar function space.
+
+    For instance, a tensorial function space of dim d = 3 that is based on a P1 (Lagrangian) scalar function space with
+    n shape functions N_1, ..., N_n has the shape functions
+
+      (N_1, 0, 0),
+      ...
+      (N_n, 0, 0),
+      (0, N_1, 0),
+      ...
+      (0, N_n, 0),
+      (0, 0, N_1),
+      ...
+      (0, 0, N_n).
+    """
+
+    def __init__(self, function_space: FunctionSpace, dim: int = None):
+        """
+        Initializes a tensorial vector function space from a scalar function space.
+
+        :param function_space: the (scalar) component function space
+        :param dim: optional parameter that is set to the dimensionality of the element later if left None here
+        """
         self._component_function_space = function_space
-        self._component = component
+        self._dim = None
 
     @property
     def is_vectorial(self) -> bool:
@@ -286,24 +324,22 @@ class TensorialVectorFunctionSpace(FunctionSpace):
     def family(self) -> str:
         return self._component_function_space.family
 
-    @property
-    def component(self) -> int:
-        return self._component
-
     @property
     def is_continuous(self) -> bool:
         return self._component_function_space.is_continuous
 
-    def _to_vector(self, phi: sp.Expr, geometry: ElementGeometry) -> sp.MatrixBase:
-        dimensions = geometry.dimensions
+    def _to_vector(
+        self, phi: sp.MatrixBase, component: int, dimensions: int
+    ) -> sp.MatrixBase:
+        if phi.shape != (1, 1):
+            raise HOGException("Component of tensorial space must be scalar.")
         return sp.Matrix(
-            [
-                [phi if c == self._component else sp.sympify(0)]
-                for c in range(dimensions)
-            ]
+            [[phi if c == component else sp.sympify(0)] for c in range(dimensions)]
         )
 
-    def _to_matrix(self, grad_phi: sp.Expr, geometry: ElementGeometry) -> sp.MatrixBase:
+    def _to_matrix(
+        self, grad_phi: sp.MatrixBase, geometry: ElementGeometry
+    ) -> sp.MatrixBase:
         grad_phi = grad_phi.transpose().tolist()[0]
         dimensions = geometry.dimensions
         zero = [sp.sympify(0) for i in range(dimensions)]
@@ -316,40 +352,30 @@ class TensorialVectorFunctionSpace(FunctionSpace):
         geometry: ElementGeometry,
         domain: str = "reference",
         dof_map: Optional[List[int]] = None,
-    ) -> List[sp.Expr]:
-        shape_functions = self._component_function_space.shape(
-            geometry, domain, dof_map
-        )
-        return [self._to_vector(phi, geometry) for phi in shape_functions]
-
-    def grad_shape(
-        self,
-        geometry: ElementGeometry,
-        domain: str = "reference",
-        dof_map: Optional[List[int]] = None,
     ) -> List[sp.MatrixBase]:
-        grad_shape_functions = self._component_function_space.grad_shape(
+        # Defaulting to the dimensionality of the geometry if not specified otherwise.
+        dim = geometry.dimensions
+        if self._dim:
+            dim = self._dim
+
+        shape_functions = self._component_function_space.shape(
             geometry, domain, dof_map
         )
         return [
-            self._to_matrix(grad_shape, geometry) for grad_shape in grad_shape_functions
+            self._to_vector(phi, c, dim) for c in range(dim) for phi in shape_functions
         ]
 
-    def num_dofs(self, geometry: ElementGeometry) -> int:
-        """Returns the number of DoFs per element."""
-        return len(self.shape(geometry))
-
     def __eq__(self, other: Any) -> bool:
         if type(self) != type(other):
             return False
         if not hasattr(other, "_component_function_space"):
             return False
-        if self._component != other._component:
+        if self._dim != other._dim:
             return False
         return self._component_function_space == other._component_function_space
 
     def __str__(self):
-        return f"Vectorial({self._component_function_space})"
+        return f"TensorialVectorSpace({self._component_function_space})"
 
     def __repr__(self):
         return str(self)
diff --git a/hog/math_helpers.py b/hog/math_helpers.py
index 6274055..eca3b78 100644
--- a/hog/math_helpers.py
+++ b/hog/math_helpers.py
@@ -14,7 +14,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-from typing import List
+from typing import List, Union
 import sympy as sp
 
 from hog.exception import HOGException
@@ -46,9 +46,13 @@ def abs(expr: sp.Expr) -> sp.Expr:
     return sp.Abs(expr)
 
 
-def grad(f: sp.Expr, symbols: List[sp.Symbol]) -> sp.MatrixBase:
+def grad(f: Union[sp.Expr, sp.MatrixBase], symbols: List[sp.Symbol]) -> sp.MatrixBase:
     """Returns the gradient of the passed sympy expression with respect to the passed symbols."""
-    return sp.simplify(sp.Matrix([[sp.diff(f, s)] for s in symbols]))
+    if isinstance(f, sp.MatrixBase):
+        return sp.simplify(f.jacobian(symbols).T)
+    elif isinstance(f, sp.Expr):
+        return sp.simplify(sp.Matrix([[sp.diff(f, s)] for s in symbols]))
+    raise HOGException("Invalid data type in grad().")
 
 
 def curl(u: sp.Matrix, symbols: List[sp.Symbol]) -> sp.Expr:
diff --git a/hog_tests/test_function_spaces.py b/hog_tests/test_function_spaces.py
new file mode 100644
index 0000000..1ffa604
--- /dev/null
+++ b/hog_tests/test_function_spaces.py
@@ -0,0 +1,66 @@
+# HyTeG Operator Generator
+# Copyright (C) 2024  HyTeG Team
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import sympy as sp
+from hog.element_geometry import TriangleElement
+from hog.function_space import LagrangianFunctionSpace, TensorialVectorFunctionSpace
+from hog.symbolizer import Symbolizer
+from hog.exception import HOGException
+
+
+def test_function_spaces():
+    symbolizer = Symbolizer()
+    geometry = TriangleElement()
+
+    print()
+
+    f = LagrangianFunctionSpace(1, symbolizer)
+    f_shape = f.shape(geometry)
+    f_grad_shape = f.grad_shape(geometry)
+    print(f)
+    print()
+    print("- shape")
+    for s in f_shape:
+        if s.shape != (1, 1):
+            raise HOGException("Shape function has wrong shape (sic).")
+        sp.pprint(s)
+    print()
+    print("- grad shape")
+    for g in f_grad_shape:
+        if g.shape != (geometry.dimensions, 1):
+            raise HOGException("Gradient has wrong shape.")
+        sp.pprint(g)
+
+    print()
+    print()
+
+    f_scalar = LagrangianFunctionSpace(1, symbolizer)
+    f = TensorialVectorFunctionSpace(f_scalar)
+    f_shape = f.shape(geometry)
+    f_grad_shape = f.grad_shape(geometry)
+    print(f)
+    print()
+    print("- shape")
+    for s in f_shape:
+        if s.shape != (geometry.dimensions, 1):
+            raise HOGException("Shape function has wrong shape (sic).")
+        sp.pprint(s)
+    print()
+    print("- grad shape")
+    for g in f_grad_shape:
+        if g.shape != (geometry.dimensions, geometry.dimensions):
+            raise HOGException("Gradient has wrong shape.")
+        sp.pprint(g)
-- 
GitLab


From c72965a51de26ca0e8a6229f573400eae8392471 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Fri, 26 Apr 2024 16:02:47 +0200
Subject: [PATCH 02/33] First apparently working version of tensorial vector
 function operators.

Instead of having dedicated forms, the diffusion form has been modified slightly by replacing the dot product with a double contraction. This should ensure compatibility with the scalar version.

* only P1
* only 2D (3D should be straightforward)
* only briefly tested vector Laplace
* some cleanup pending
---
 generate_all_operators.py                     |  12 +-
 hog/forms.py                                  |   6 +-
 hog/function_space.py                         |  25 +--
 .../function_space_impls.py                   | 195 +++++++++++++++++-
 hog/operator_generation/kernel_types.py       |   4 +-
 5 files changed, 213 insertions(+), 29 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 47c7c9e..c9c1061 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -43,7 +43,12 @@ from hog.forms import (
     nonlinear_diffusion_newton_galerkin,
 )
 from hog.forms_vectorial import curl_curl, curl_curl_plus_mass, mass_n1e1
-from hog.function_space import FunctionSpace, LagrangianFunctionSpace, N1E1Space
+from hog.function_space import (
+    FunctionSpace,
+    LagrangianFunctionSpace,
+    N1E1Space,
+    TensorialVectorFunctionSpace,
+)
 from hog.logger import get_logger, TimedLogger
 from hog.operator_generation.kernel_types import (
     Apply,
@@ -516,6 +521,7 @@ def all_operators(
     blending: GeometryMap,
 ) -> List[OperatorInfo]:
     P1 = LagrangianFunctionSpace(1, symbolizer)
+    P1Vector = TensorialVectorFunctionSpace(P1)
     P2 = LagrangianFunctionSpace(2, symbolizer)
     N1E1 = N1E1Space(symbolizer)
 
@@ -556,6 +562,10 @@ def all_operators(
                             coefficient_function_space=P1, onlyNewtonGalerkinPartOfForm=False),
                             type_descriptor=type_descriptor, opts=opts, blending=blending))
 
+    ops.append(OperatorInfo(mapping="P1Vector", name="Diffusion", trial_space=P1Vector, test_space=P1Vector,
+                            form=diffusion, type_descriptor=type_descriptor, opts=opts, blending=blending,
+                            geometries=[TriangleElement()]))
+
     # fmt: on
 
     for c in [0, 1, 2]:
diff --git a/hog/forms.py b/hog/forms.py
index dc98e44..0e2378c 100644
--- a/hog/forms.py
+++ b/hog/forms.py
@@ -79,7 +79,7 @@ Weak formulation
     u: trial function (space: {trial})
     v: test function  (space: {test})
     
-    ∫ ∇u · ∇v
+    ∫ ∇u : ∇v
 """
 
     if trial != test:
@@ -124,7 +124,7 @@ Weak formulation
                     jac_affine_inv.T * grad_psi,
                 )
                 form = (
-                    dot(
+                    double_contraction(
                         jac_blending_inv.T
                         * sp.Matrix(jac_affine_inv_T_grad_phi_symbols),
                         jac_blending_inv.T
@@ -137,7 +137,7 @@ Weak formulation
                 jac_affine_inv_grad_phi_jac_affine_inv_grad_psi_det_symbol = (
                     tabulation.register_factor(
                         "jac_affine_inv_grad_phi_jac_affine_inv_grad_psi_det",
-                        dot(
+                        double_contraction(
                             jac_affine_inv.T * grad_phi,
                             jac_affine_inv.T * grad_psi,
                         )
diff --git a/hog/function_space.py b/hog/function_space.py
index aa70ef3..67dfe74 100644
--- a/hog/function_space.py
+++ b/hog/function_space.py
@@ -298,15 +298,13 @@ class TensorialVectorFunctionSpace(FunctionSpace):
       (0, 0, N_n).
     """
 
-    def __init__(self, function_space: FunctionSpace, dim: int = None):
+    def __init__(self, function_space: FunctionSpace):
         """
         Initializes a tensorial vector function space from a scalar function space.
 
         :param function_space: the (scalar) component function space
-        :param dim: optional parameter that is set to the dimensionality of the element later if left None here
         """
         self._component_function_space = function_space
-        self._dim = None
 
     @property
     def is_vectorial(self) -> bool:
@@ -328,6 +326,10 @@ class TensorialVectorFunctionSpace(FunctionSpace):
     def is_continuous(self) -> bool:
         return self._component_function_space.is_continuous
 
+    @property
+    def component_function_space(self) -> FunctionSpace:
+        return self._component_function_space
+
     def _to_vector(
         self, phi: sp.MatrixBase, component: int, dimensions: int
     ) -> sp.MatrixBase:
@@ -337,30 +339,19 @@ class TensorialVectorFunctionSpace(FunctionSpace):
             [[phi if c == component else sp.sympify(0)] for c in range(dimensions)]
         )
 
-    def _to_matrix(
-        self, grad_phi: sp.MatrixBase, geometry: ElementGeometry
-    ) -> sp.MatrixBase:
-        grad_phi = grad_phi.transpose().tolist()[0]
-        dimensions = geometry.dimensions
-        zero = [sp.sympify(0) for i in range(dimensions)]
-        return sp.Matrix(
-            [(grad_phi if c == self._component else zero) for c in range(dimensions)]
-        )
-
     def shape(
         self,
         geometry: ElementGeometry,
         domain: str = "reference",
         dof_map: Optional[List[int]] = None,
     ) -> List[sp.MatrixBase]:
-        # Defaulting to the dimensionality of the geometry if not specified otherwise.
+
         dim = geometry.dimensions
-        if self._dim:
-            dim = self._dim
 
         shape_functions = self._component_function_space.shape(
             geometry, domain, dof_map
         )
+
         return [
             self._to_vector(phi, c, dim) for c in range(dim) for phi in shape_functions
         ]
@@ -370,8 +361,6 @@ class TensorialVectorFunctionSpace(FunctionSpace):
             return False
         if not hasattr(other, "_component_function_space"):
             return False
-        if self._dim != other._dim:
-            return False
         return self._component_function_space == other._component_function_space
 
     def __str__(self):
diff --git a/hog/operator_generation/function_space_impls.py b/hog/operator_generation/function_space_impls.py
index ac8f8a9..fc92240 100644
--- a/hog/operator_generation/function_space_impls.py
+++ b/hog/operator_generation/function_space_impls.py
@@ -27,7 +27,12 @@ from pystencils.backends.cbackend import CustomCodeNode
 
 from hog.element_geometry import ElementGeometry
 from hog.exception import HOGException
-from hog.function_space import FunctionSpace, LagrangianFunctionSpace, N1E1Space
+from hog.function_space import (
+    FunctionSpace,
+    LagrangianFunctionSpace,
+    TensorialVectorFunctionSpace,
+    N1E1Space,
+)
 from hog.operator_generation.indexing import (
     CellType,
     FaceType,
@@ -95,6 +100,18 @@ class FunctionSpaceImpl(ABC):
                 impl_class = P1FunctionSpaceImpl
             else:
                 raise HOGException("Lagrangian function space must be of order 1 or 2.")
+        elif isinstance(func_space, TensorialVectorFunctionSpace):
+            if isinstance(func_space.component_function_space, LagrangianFunctionSpace):
+                if func_space.component_function_space.degree == 1:
+                    impl_class = P1VectorFunctionSpaceImpl
+                else:
+                    raise HOGException(
+                        "TensorialVectorFunctionSpaces are only supported for P1 components."
+                    )
+            else:
+                raise HOGException(
+                    "TensorialVectorFunctionSpaces are only supported with Lagrangian component spaces."
+                )
         elif isinstance(func_space, N1E1Space):
             impl_class = N1E1FunctionSpaceImpl
         else:
@@ -136,7 +153,7 @@ class FunctionSpaceImpl(ABC):
         ...
 
     @abstractmethod
-    def invert_elementwise(self) -> str:
+    def invert_elementwise(self, dim: int) -> str:
         """C++ code for inverting each DoF of the linalg vector."""
         ...
 
@@ -268,7 +285,7 @@ class P1FunctionSpaceImpl(FunctionSpaceImpl):
 
         return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {macro}.getData( {self._deref()}.get{Macro}DataID() )->getPointer( level );"
 
-    def invert_elementwise(self) -> str:
+    def invert_elementwise(self, dim: int) -> str:
         return f"{self._deref()}.invertElementwise( level );"
 
     def local_dofs(
@@ -394,7 +411,7 @@ class P2FunctionSpaceImpl(FunctionSpaceImpl):
             f"{self.type_descriptor.pystencils_type}* _data_{self.name}Edge   = {macro}.getData( {self._deref()}.getEdgeDoFFunction().get{Macro}DataID() )->getPointer( level );"
         )
 
-    def invert_elementwise(self) -> str:
+    def invert_elementwise(self, dim: int) -> str:
         return f"{self._deref()}.invertElementwise( level );"
 
     def local_dofs(
@@ -442,6 +459,174 @@ class P2FunctionSpaceImpl(FunctionSpaceImpl):
         )
 
 
+class P1VectorFunctionSpaceImpl(FunctionSpaceImpl):
+    def __init__(
+        self,
+        fe_space: FunctionSpace,
+        name: str,
+        type_descriptor: HOGType,
+        is_pointer: bool = False,
+    ):
+        super().__init__(fe_space, name, type_descriptor, is_pointer)
+        self.fields = {}
+
+    def _field_name(self, component: int) -> str:
+        return self.name + f"_{component}"
+
+    def _raw_pointer_name(self, component: int) -> str:
+        return f"_data_" + self._field_name(component)
+
+    def pre_communication(self, dim: int) -> str:
+        if dim == 2:
+            return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );"
+        else:
+            return (
+                f"{self._deref()}.communicate< Face, Cell >( level );\n"
+                f"{self._deref()}.communicate< Edge, Cell >( level );\n"
+                f"{self._deref()}.communicate< Vertex, Cell >( level );"
+            )
+
+    def zero_halos(self, dim: int) -> str:
+        if dim == 2:
+            return (
+                f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) ) {{\n"
+                f"    if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) ) {{\n"
+                f"        auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n"
+                f"        {self._raw_pointer_name(0)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"        {self._raw_pointer_name(1)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"    }}\n"
+                f"}}"
+            )
+        else:
+            return (
+                f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) ) {{\n"
+                f"    if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() ) {{\n"
+                f"        auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n"
+                f"        {self._raw_pointer_name(0)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"        {self._raw_pointer_name(1)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"        {self._raw_pointer_name(2)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"    }}\n"
+                f"}}"
+            )
+
+    def post_communication(
+        self, dim: int, params: str, transform_basis: bool = True
+    ) -> str:
+        if dim == 2:
+            ret_str = ""
+            for i in range(dim):
+                ret_str += (
+                f"{self._deref()}[{i}].communicateAdditively < Face, Edge > ( {params} );\n"
+                f"{self._deref()}[{i}].communicateAdditively < Face, Vertex > ( {params} );\n"
+            )
+            return ret_str
+        else:
+            return (
+                f"{self._deref()}.communicateAdditively< Cell, Face >( {params} );\n"
+                f"{self._deref()}.communicateAdditively< Cell, Edge >( {params} );\n"
+                f"{self._deref()}.communicateAdditively< Cell, Vertex >( {params} );"
+            )
+
+    def pointer_retrieval(self, dim: int) -> str:
+        """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d)."""
+        Macro = {2: "Face", 3: "Cell"}[dim]
+        macro = {2: "face", 3: "cell"}[dim]
+
+        ret_str = ""
+        for i in range(dim):
+            ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i)} = {macro}.getData( {self._deref()}[{i}].get{Macro}DataID() )->getPointer( level );\n"
+        return ret_str
+
+    def invert_elementwise(self, dim: int) -> str:
+        ret_str = ""
+        for i in range(dim):
+            ret_str += f"{self._deref()}[{i}].invertElementwise( level );\n"
+        return ret_str
+
+    def local_dofs(
+        self,
+        geometry: ElementGeometry,
+        element_index: Tuple[int, int, int],
+        element_type: Union[FaceType, CellType],
+        indexing_info: IndexingInfo,
+    ) -> List[Field.Access]:
+        """
+        Returns the element-local DoFs (field accesses) in a list (i.e., linearized).
+
+        The order here has to match that of the function space implementation.
+        TODO: This is a little concerning since that order has to match in two very different parts of this
+              implementation. Here, and in the TensorialVectorFunctionSpace. Maybe we can define this order in a single
+              location.
+        We return them in "SoA" order: first all DoFs corresponding to the function with the first component != 0,
+        then all DoFs corresponding to the function with the second component != 0 etc.
+
+        For instance, in 2D we get the DoFs corresponding to the 6 shape functions in the following order:
+
+        First component != 0
+
+        list[0]: ⎡-x_ref_0 - x_ref_1 + 1⎤
+                 ⎢                      ⎥
+                 ⎣          0           ⎦
+
+        list[1]: ⎡x_ref_0⎤
+                 ⎢       ⎥
+                 ⎣   0   ⎦
+
+        list[2]: ⎡x_ref_1⎤
+                 ⎢       ⎥
+                 ⎣   0   ⎦
+
+        Second component != 0
+
+        list[3]: ⎡          0           ⎤
+                 ⎢                      ⎥
+                 ⎣-x_ref_0 - x_ref_1 + 1⎦
+
+        list[4]: ⎡   0   ⎤
+                 ⎢       ⎥
+                 ⎣x_ref_0⎦
+
+        list[5]: ⎡   0   ⎤
+                 ⎢       ⎥
+                 ⎣x_ref_1⎦
+        """
+
+        for c in range(geometry.dimensions):
+            if c not in self.fields:
+                self.fields[c] = self._create_field(self._field_name(c))
+
+        vertex_dof_indices = micro_element_to_vertex_indices(
+            geometry, element_type, element_index
+        )
+        vertex_array_indices = [
+            dof_idx.array_index(geometry, indexing_info)
+            for dof_idx in vertex_dof_indices
+        ]
+
+        return [
+            self.fields[c].absolute_access((idx,), (0,))
+            for c in range(geometry.dimensions)
+            for idx in vertex_array_indices
+        ]
+
+    def func_type_string(self) -> str:
+        return f"P1VectorFunction< {self.type_descriptor.pystencils_type} >"
+
+    def includes(self) -> Set[str]:
+        return {f"hyteg/p1functionspace/P1VectorFunction.hpp"}
+
+    def dof_transformation(
+        self,
+        geometry: ElementGeometry,
+        element_index: Tuple[int, int, int],
+        element_type: Union[FaceType, CellType],
+    ) -> Tuple[CustomCodeNode, sp.MatrixBase]:
+        return (
+            CustomCodeNode("", [], []),
+            sp.Identity(self.fe_space.num_dofs(geometry)),
+        )
+
+
 class N1E1FunctionSpaceImpl(FunctionSpaceImpl):
     def __init__(
         self,
@@ -489,7 +674,7 @@ class N1E1FunctionSpaceImpl(FunctionSpaceImpl):
 
         return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {macro}.getData( {self._deref()}.getDoFs()->get{Macro}DataID() )->getPointer( level );"
 
-    def invert_elementwise(self) -> str:
+    def invert_elementwise(self, dim: int) -> str:
         return f"{self._deref()}.getDoFs()->invertElementwise( level );"
 
     def local_dofs(
diff --git a/hog/operator_generation/kernel_types.py b/hog/operator_generation/kernel_types.py
index a474d9c..c2c781a 100644
--- a/hog/operator_generation/kernel_types.py
+++ b/hog/operator_generation/kernel_types.py
@@ -387,7 +387,7 @@ class AssembleDiagonal(KernelType):
             f"\n"
             f"for ( uint_t level = minLevel_; level <= maxLevel_; level++ )\n"
             f"{{\n"
-            f"    {self.dst.name}->setToZero( level );\n"
+            f"    {self.dst.name}->interpolate( 0, level );\n"
             f"\n"
             f"    if ( storage_->hasGlobalCells() )\n"
             f"    {{\n"
@@ -406,7 +406,7 @@ class AssembleDiagonal(KernelType):
             f"{indent(macro_loop(2), 2 * INDENT)}\n"
             f"    }}\n"
             f"\n"
-            f"{indent(self.dst.invert_elementwise(), INDENT)}\n"
+            f"{indent(self.dst.invert_elementwise(dim=0), INDENT)}\n"
             f"}}\n"
             f"\n"
             f'this->stopTiming( "{self.name}" );'
-- 
GitLab


From 1b4059c8e08adf2a6aaf8a02815f6eee3d660f0f Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 30 Apr 2024 08:54:21 +0200
Subject: [PATCH 03/33] Extended vector ops to 3D (only P1 for now).

---
 generate_all_operators.py                     |  3 +-
 .../function_space_impls.py                   | 36 +++++++++++--------
 2 files changed, 22 insertions(+), 17 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index c9c1061..4dfe899 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -563,8 +563,7 @@ def all_operators(
                             type_descriptor=type_descriptor, opts=opts, blending=blending))
 
     ops.append(OperatorInfo(mapping="P1Vector", name="Diffusion", trial_space=P1Vector, test_space=P1Vector,
-                            form=diffusion, type_descriptor=type_descriptor, opts=opts, blending=blending,
-                            geometries=[TriangleElement()]))
+                            form=diffusion, type_descriptor=type_descriptor, opts=opts, blending=blending))
 
     # fmt: on
 
diff --git a/hog/operator_generation/function_space_impls.py b/hog/operator_generation/function_space_impls.py
index fc92240..276b362 100644
--- a/hog/operator_generation/function_space_impls.py
+++ b/hog/operator_generation/function_space_impls.py
@@ -19,7 +19,7 @@ from abc import ABC, abstractmethod
 import itertools
 import numpy as np
 import sympy as sp
-from typing import List, Tuple, Set, Union
+from typing import List, Tuple, Set, Union, Dict
 
 import pystencils as ps
 from pystencils import Field, FieldType
@@ -468,7 +468,7 @@ class P1VectorFunctionSpaceImpl(FunctionSpaceImpl):
         is_pointer: bool = False,
     ):
         super().__init__(fe_space, name, type_descriptor, is_pointer)
-        self.fields = {}
+        self.fields: Dict = {}
 
     def _field_name(self, component: int) -> str:
         return self.name + f"_{component}"
@@ -480,11 +480,14 @@ class P1VectorFunctionSpaceImpl(FunctionSpaceImpl):
         if dim == 2:
             return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );"
         else:
-            return (
-                f"{self._deref()}.communicate< Face, Cell >( level );\n"
-                f"{self._deref()}.communicate< Edge, Cell >( level );\n"
-                f"{self._deref()}.communicate< Vertex, Cell >( level );"
-            )
+            ret_str = ""
+            for i in range(dim):
+                ret_str += (
+                    f"{self._deref()}[{i}].communicate< Face, Cell >( level );\n"
+                    f"{self._deref()}[{i}].communicate< Edge, Cell >( level );\n"
+                    f"{self._deref()}[{i}].communicate< Vertex, Cell >( level );"
+                )
+            return ret_str
 
     def zero_halos(self, dim: int) -> str:
         if dim == 2:
@@ -516,16 +519,19 @@ class P1VectorFunctionSpaceImpl(FunctionSpaceImpl):
             ret_str = ""
             for i in range(dim):
                 ret_str += (
-                f"{self._deref()}[{i}].communicateAdditively < Face, Edge > ( {params} );\n"
-                f"{self._deref()}[{i}].communicateAdditively < Face, Vertex > ( {params} );\n"
-            )
+                    f"{self._deref()}[{i}].communicateAdditively < Face, Edge > ( {params} );\n"
+                    f"{self._deref()}[{i}].communicateAdditively < Face, Vertex > ( {params} );\n"
+                )
             return ret_str
         else:
-            return (
-                f"{self._deref()}.communicateAdditively< Cell, Face >( {params} );\n"
-                f"{self._deref()}.communicateAdditively< Cell, Edge >( {params} );\n"
-                f"{self._deref()}.communicateAdditively< Cell, Vertex >( {params} );"
-            )
+            ret_str = ""
+            for i in range(dim):
+                ret_str += (
+                    f"{self._deref()}[{i}].communicateAdditively< Cell, Face >( {params} );\n"
+                    f"{self._deref()}[{i}].communicateAdditively< Cell, Edge >( {params} );\n"
+                    f"{self._deref()}[{i}].communicateAdditively< Cell, Vertex >( {params} );"
+                )
+            return ret_str
 
     def pointer_retrieval(self, dim: int) -> str:
         """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d)."""
-- 
GitLab


From 55ae07cc89c29914f635ba813fd2610b59a847e1 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Mon, 6 May 2024 14:17:24 +0200
Subject: [PATCH 04/33] Extended vector ops to P2.

---
 hog/forms.py                                  |   5 +-
 .../function_space_impls.py                   | 187 +++++++++++++++++-
 2 files changed, 189 insertions(+), 3 deletions(-)

diff --git a/hog/forms.py b/hog/forms.py
index 0e2378c..3b6c72a 100644
--- a/hog/forms.py
+++ b/hog/forms.py
@@ -80,6 +80,10 @@ Weak formulation
     v: test function  (space: {test})
     
     ∫ ∇u : ∇v
+    
+    Note that the double contraction (:) reduces to the dot product for scalar function spaces, i.e. the form becomes
+    
+    ∫ ∇u · ∇v 
 """
 
     if trial != test:
@@ -90,7 +94,6 @@ Weak formulation
     with TimedLogger("assembling diffusion matrix", level=logging.DEBUG):
         tabulation = Tabulation(symbolizer)
 
-        jac_affine = symbolizer.jac_ref_to_affine(geometry.dimensions)
         jac_affine_inv = symbolizer.jac_ref_to_affine_inv(geometry.dimensions)
         jac_affine_det = symbolizer.abs_det_jac_ref_to_affine()
 
diff --git a/hog/operator_generation/function_space_impls.py b/hog/operator_generation/function_space_impls.py
index 276b362..130539f 100644
--- a/hog/operator_generation/function_space_impls.py
+++ b/hog/operator_generation/function_space_impls.py
@@ -104,9 +104,11 @@ class FunctionSpaceImpl(ABC):
             if isinstance(func_space.component_function_space, LagrangianFunctionSpace):
                 if func_space.component_function_space.degree == 1:
                     impl_class = P1VectorFunctionSpaceImpl
+                elif func_space.component_function_space.degree == 2:
+                    impl_class = P2VectorFunctionSpaceImpl
                 else:
                     raise HOGException(
-                        "TensorialVectorFunctionSpaces are only supported for P1 components."
+                        "TensorialVectorFunctionSpaces not supported for the chosen components."
                     )
             else:
                 raise HOGException(
@@ -468,7 +470,7 @@ class P1VectorFunctionSpaceImpl(FunctionSpaceImpl):
         is_pointer: bool = False,
     ):
         super().__init__(fe_space, name, type_descriptor, is_pointer)
-        self.fields: Dict = {}
+        self.fields: Dict[int, Field] = {}
 
     def _field_name(self, component: int) -> str:
         return self.name + f"_{component}"
@@ -633,6 +635,187 @@ class P1VectorFunctionSpaceImpl(FunctionSpaceImpl):
         )
 
 
+class P2VectorFunctionSpaceImpl(FunctionSpaceImpl):
+    def __init__(
+        self,
+        fe_space: FunctionSpace,
+        name: str,
+        type_descriptor: HOGType,
+        is_pointer: bool = False,
+    ):
+        super().__init__(fe_space, name, type_descriptor, is_pointer)
+        self.fields: Dict[Tuple[str, int], Field] = {}
+
+    def _field_name(self, component: int, dof_type: str) -> str:
+        """dof_type should either be 'vertex' or 'edge'"""
+        return self.name + f"_{dof_type}_{component}"
+
+    def _raw_pointer_name(self, component: int, dof_type: str) -> str:
+        """dof_type should either be 'vertex' or 'edge'"""
+        return f"_data_" + self._field_name(component, dof_type)
+
+    def pre_communication(self, dim: int) -> str:
+        if dim == 2:
+            return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );"
+        else:
+            ret_str = ""
+            for i in range(dim):
+                ret_str += (
+                    f"{self._deref()}[{i}].communicate< Face, Cell >( level );\n"
+                    f"{self._deref()}[{i}].communicate< Edge, Cell >( level );\n"
+                    f"{self._deref()}[{i}].communicate< Vertex, Cell >( level );"
+                )
+            return ret_str
+
+    def zero_halos(self, dim: int) -> str:
+        if dim == 2:
+            return (
+                f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) )\n"
+                f"{{\n"
+                f"    if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) )\n"
+                f"    {{\n"
+                f"        auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n"
+                f"        {self._raw_pointer_name(0, 'vertex')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n"
+                f"        {self._raw_pointer_name(1, 'vertex')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n"
+                f"    }}\n"
+                f"}}\n"
+                f"for ( const auto& idx : edgedof::macroface::Iterator( level ) )\n"
+                f"{{\n"
+                f"    for ( const auto& orientation : edgedof::faceLocalEdgeDoFOrientations )\n"
+                f"    {{\n"
+                f"        if ( !edgedof::macroface::isInnerEdgeDoF( level, idx, orientation ) )\n"
+                f"        {{\n"
+                f"            auto arrayIdx = edgedof::macroface::index( level, idx.x(), idx.y(), orientation );\n"
+                f"            {self._raw_pointer_name(0, 'edge')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n"
+                f"            {self._raw_pointer_name(1, 'edge')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n"
+                f"        }}\n"
+                f"    }}\n"
+                f"}}"
+            )
+        else:
+            return (
+                f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) )\n"
+                f"{{\n"
+                f"if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() )\n"
+                f"    {{\n"
+                f"        auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n"
+                f"        {self._raw_pointer_name(0, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"        {self._raw_pointer_name(1, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"        {self._raw_pointer_name(2, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n"
+                f"    }}\n"
+                f"}}\n"
+                f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[0].getEdgeDoFFunction().getCellDataID() );\n"
+                f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[1].getEdgeDoFFunction().getCellDataID() );\n"
+                f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[2].getEdgeDoFFunction().getCellDataID() );\n"
+            )
+
+    def post_communication(
+        self, dim: int, params: str, transform_basis: bool = True
+    ) -> str:
+        if dim == 2:
+            ret_str = ""
+            for i in range(dim):
+                ret_str += (
+                    f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Face, Edge >( {params} );\n"
+                    f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Face, Vertex >( {params} );\n"
+                    f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Face, Edge >( {params} );"
+                )
+            return ret_str
+        else:
+            ret_str = ""
+            for i in range(dim):
+                ret_str += (
+                    f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Face >( {params} );\n"
+                    f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Edge >( {params} );\n"
+                    f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Vertex >( {params} );\n"
+                    f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Cell, Face >( {params} );\n"
+                    f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Cell, Edge >( {params} );"
+                )
+            return ret_str
+
+    def pointer_retrieval(self, dim: int) -> str:
+        """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d)."""
+        Macro = {2: "Face", 3: "Cell"}[dim]
+        macro = {2: "face", 3: "cell"}[dim]
+
+        ret_str = ""
+        for i in range(dim):
+            ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i, 'vertex')} = {macro}.getData( {self._deref()}[{i}].getVertexDoFFunction().get{Macro}DataID() )->getPointer( level );\n"
+            ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i, 'edge')} = {macro}.getData( {self._deref()}[{i}].getEdgeDoFFunction().get{Macro}DataID() )->getPointer( level );\n"
+        return ret_str
+
+    def invert_elementwise(self, dim: int) -> str:
+        ret_str = ""
+        for i in range(dim):
+            ret_str += f"{self._deref()}[{i}].invertElementwise( level );\n"
+        return ret_str
+
+    def local_dofs(
+        self,
+        geometry: ElementGeometry,
+        element_index: Tuple[int, int, int],
+        element_type: Union[FaceType, CellType],
+        indexing_info: IndexingInfo,
+    ) -> List[Field.Access]:
+        """
+        Returns the element-local DoFs (field accesses) in a list (i.e., linearized).
+
+        See P1VectorFunctionSpaceImpl::local_dofs() for details.
+        """
+
+        for dt in ["vertex", "edge"]:
+            for c in range(geometry.dimensions):
+                if (dt, c) not in self.fields:
+                    self.fields[(dt, c)] = self._create_field(self._field_name(c, dt))
+
+        vertex_dof_indices = micro_element_to_vertex_indices(
+            geometry, element_type, element_index
+        )
+
+        edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices)
+
+        vertex_array_indices = [
+            dof_idx.array_index(geometry, indexing_info)
+            for dof_idx in vertex_dof_indices
+        ]
+
+        edge_array_indices = [
+            dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices
+        ]
+
+        loc_dofs = []
+
+        for c in range(geometry.dimensions):
+            loc_dofs += [
+                self.fields[("vertex", c)].absolute_access((idx,), (0,))
+                for idx in vertex_array_indices
+            ]
+            loc_dofs += [
+                self.fields[("edge", c)].absolute_access((idx,), (0,))
+                for idx in edge_array_indices
+            ]
+
+        return loc_dofs
+
+    def func_type_string(self) -> str:
+        return f"P2VectorFunction< {self.type_descriptor.pystencils_type} >"
+
+    def includes(self) -> Set[str]:
+        return {f"hyteg/p2functionspace/P2VectorFunction.hpp",
+                f"hyteg/p2functionspace/P2Function.hpp"}
+
+    def dof_transformation(
+        self,
+        geometry: ElementGeometry,
+        element_index: Tuple[int, int, int],
+        element_type: Union[FaceType, CellType],
+    ) -> Tuple[CustomCodeNode, sp.MatrixBase]:
+        return (
+            CustomCodeNode("", [], []),
+            sp.Identity(self.fe_space.num_dofs(geometry)),
+        )
+
+
 class N1E1FunctionSpaceImpl(FunctionSpaceImpl):
     def __init__(
         self,
-- 
GitLab


From 30200e188d840f3858f874b42070ecc1eaab2b61 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Mon, 13 May 2024 13:47:32 +0200
Subject: [PATCH 05/33] Some debugging output in generate_all_operators.py
 (mainly for the CI).

---
 generate_all_operators.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 4dfe899..d0d76fb 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -15,6 +15,7 @@
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 import argparse
+import sys
 from argparse import ArgumentParser
 from dataclasses import dataclass, field
 from functools import partial
@@ -280,6 +281,8 @@ def main():
     logger = get_logger(log_level)
     TimedLogger.set_log_level(log_level)
 
+    logger.info(f"Running {__file__} with arguments: {' '.join(sys.argv[1:])}")
+
     symbolizer = Symbolizer()
 
     if args.list_quadratures:
-- 
GitLab


From ac8f91ff301632c36cc0713c7def643289583e1e Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Wed, 15 May 2024 16:02:26 +0200
Subject: [PATCH 06/33] Using re.fullmatch also for the FE spaces in the
 generate_all_operators.py script. Otherwise, e.g., 'P1Vector' is also chosen
 if only 'P1' is specified.

---
 generate_all_operators.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index d0d76fb..b9b7d1c 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -357,7 +357,8 @@ def main():
 
     filtered_operators = list(
         filter(
-            lambda o: re_mapping.search(o.mapping) and re.fullmatch(args.form, o.name),
+            lambda o: re.fullmatch(args.space_mapping, o.mapping)
+            and re.fullmatch(args.form, o.name),
             operators,
         )
     )
-- 
GitLab


From 98f9c4396d97df9624d372276fcd1d71ec34ce66 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Wed, 15 May 2024 16:03:28 +0200
Subject: [PATCH 07/33] Updating the epsilon form to also work with (tensorial)
 vector spaces to test offdiagonal blocks.

---
 hog/forms.py | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/hog/forms.py b/hog/forms.py
index 3b6c72a..d9ba81b 100644
--- a/hog/forms.py
+++ b/hog/forms.py
@@ -525,11 +525,22 @@ def epsilon(
     variable_viscosity: bool = True,
     coefficient_function_space: Optional[FunctionSpace] = None,
 ) -> Form:
+
+    if trial.is_vectorial ^ test.is_vectorial:
+        raise HOGException(
+            "Either both (trial and test) spaces or none should be vectorial."
+        )
+
+    vectorial_spaces = trial.is_vectorial
+    docstring_components = (
+        ""
+        if vectorial_spaces
+        else f"\nComponent trial: {component_trial}\nComponent test:  {component_test}"
+    )
+
     docstring = f"""
 "Epsilon" operator.
-
-Component trial: {component_trial}
-Component test:  {component_test}
+{docstring_components}
 Geometry map:    {blending}
 
 Weak formulation
@@ -544,12 +555,17 @@ where
     
     ε(w) := (1/2) (∇w + (∇w)ᵀ)
 """
-    if variable_viscosity == False:
+    if not variable_viscosity:
         raise HOGException("Constant viscosity currently not supported.")
         # TODO fix issue with undeclared p_affines
 
-    if geometry.dimensions < 3 and (component_trial > 1 or component_test > 1):
+    if (
+        not vectorial_spaces
+        and geometry.dimensions < 3
+        and (component_trial > 1 or component_test > 1)
+    ):
         return create_empty_element_matrix(trial, test, geometry)
+
     with TimedLogger("assembling epsilon matrix", level=logging.DEBUG):
         tabulation = Tabulation(symbolizer)
 
@@ -602,7 +618,7 @@ where
             if isinstance(trial, EnrichedGalerkinFunctionSpace):
                 # for EDG, the shape function is already vectorial and does not have to be multiplied by e_vec
                 grad_phi_vec = jac_affine * grad_phi_vec
-            else:
+            elif not vectorial_spaces:
                 grad_phi_vec = (
                     (e_vec(geometry.dimensions, component_trial) * phi)
                     .jacobian(ref_symbols_list)
@@ -612,7 +628,7 @@ where
             if isinstance(test, EnrichedGalerkinFunctionSpace):
                 # for EDG, the shape function is already vectorial and does not have to be multiplied by e_vec
                 grad_psi_vec = jac_affine * grad_psi_vec
-            else:
+            elif not vectorial_spaces:
                 grad_psi_vec = (
                     (e_vec(geometry.dimensions, component_test) * psi)
                     .jacobian(ref_symbols_list)
@@ -697,7 +713,7 @@ where
     return Form(
         mat,
         tabulation,
-        symmetric=component_trial == component_test,
+        symmetric=vectorial_spaces or (component_trial == component_test),
         docstring=docstring,
     )
 
@@ -727,7 +743,7 @@ Weak formulation
     if trial != test:
         TimedLogger(
             "Trial and test space can be different, but please make sure this is intensional!",
-            level=logging.INFO
+            level=logging.INFO,
         ).log()
 
     with TimedLogger("assembling k-mass matrix", level=logging.DEBUG):
-- 
GitLab


From 01cb0a1ee9d74a8ddcbb07aae9d350b4d67e5cbc Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Wed, 15 May 2024 16:15:39 +0200
Subject: [PATCH 08/33] Attempt to add a P1VectorLaplace HyTeG-integration
 test.

---
 hyteg_integration_tests/src/CMakeLists.txt    |  9 +--
 .../src/DiffusionP1Vector.cpp                 | 61 +++++++++++++++++++
 2 files changed, 66 insertions(+), 4 deletions(-)
 create mode 100644 hyteg_integration_tests/src/DiffusionP1Vector.cpp

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 73be793..b84602b 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -44,10 +44,11 @@ add_operator_test(FILE CurlCurl.cpp                   FORM CurlCurl ABBR CVIP
 add_operator_test(FILE CurlCurlPlusMass.cpp DEF TEST_ASSEMBLE FORM CurlCurlPlusMass ABBR noopts GEN_ARGS --quad-degree 2 2)
 add_operator_test(FILE CurlCurlPlusMass.cpp                   FORM CurlCurlPlusMass ABBR VI     GEN_ARGS --quad-degree 2 2 -o MOVECONSTANTS VECTORIZE)
 
-add_operator_test(FILE DiffusionP1.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1    GEN_ARGS -s P1 --quad-degree 0 0)
-add_operator_test(FILE DiffusionP1.cpp                   FORM Diffusion ABBR 1VIP GEN_ARGS -s P1 --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE POLYCSE)
-add_operator_test(FILE DiffusionP1.cpp                   FORM Diffusion ABBR 1CVI GEN_ARGS -s P1 --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE)
-add_operator_test(FILE DiffusionP2.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 2    GEN_ARGS -s P2 --quad-degree 2 2)
+add_operator_test(FILE DiffusionP1.cpp       DEF TEST_ASSEMBLE FORM Diffusion ABBR 1       GEN_ARGS -s P1       --quad-degree 0 0)
+add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1VIP    GEN_ARGS -s P1       --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE POLYCSE)
+add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1CVI    GEN_ARGS -s P1       --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE)
+add_operator_test(FILE DiffusionP2.cpp       DEF TEST_ASSEMBLE FORM Diffusion ABBR 2       GEN_ARGS -s P2       --quad-degree 2 2)
+add_operator_test(FILE DiffusionP1Vector.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1Vec    GEN_ARGS -s P1Vector --quad-degree 0 0)
 
 add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts GEN_ARGS -s P2 --quad-degree 4 4)
 add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
diff --git a/hyteg_integration_tests/src/DiffusionP1Vector.cpp b/hyteg_integration_tests/src/DiffusionP1Vector.cpp
new file mode 100644
index 0000000..5c3424b
--- /dev/null
+++ b/hyteg_integration_tests/src/DiffusionP1Vector.cpp
@@ -0,0 +1,61 @@
+/*
+ * HyTeG Operator Generator
+ * Copyright (C) 2017-2024  Nils Kohl, Fabian Böhm, Daniel Bauer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "core/DataTypes.h"
+
+#include "hyteg/elementwiseoperators/P1ElementwiseOperator.hpp"
+#include "mixed_operator/VectorLaplaceOperator.hpp"
+
+#include "Diffusion/TestOpDiffusion.hpp"
+#include "OperatorGenerationTest.hpp"
+
+using namespace hyteg;
+using walberla::real_t;
+
+int main( int argc, char* argv[] )
+{
+   walberla::MPIManager::instance()->initializeMPI( &argc, &argv );
+   walberla::MPIManager::instance()->useWorldComm();
+
+   const uint_t level = 3;
+
+   for ( uint_t d = 2; d <= 3; ++d )
+   {
+      StorageSetup storageSetup;
+      if ( d == 2 )
+      {
+         storageSetup = StorageSetup(
+             "quad_4el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/quad_4el.msh" ), GeometryMap::Type::IDENTITY );
+      }
+      else
+      {
+         storageSetup = StorageSetup(
+             "cube_6el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/3D/cube_6el.msh" ), GeometryMap::Type::IDENTITY );
+      }
+
+      testOperators< P1VectorFunction< real_t >, P1ElementwiseVectorLaplaceOperator, operatorgeneration::TestOpDiffusion >(
+          level, storageSetup, 225, 9.0e6 );
+
+#ifdef TEST_ASSEMBLE
+      compareAssembledMatrix< P1ElementwiseLaplaceOperator, operatorgeneration::TestOpDiffusion >(
+          level, storageSetup, storageSetup.description() + " Assembly", 360 );
+#endif
+   }
+
+   return EXIT_SUCCESS;
+}
-- 
GitLab


From 05b9f9384b03a8873d487b6e3a312ff279a24e8f Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 09:51:36 +0200
Subject: [PATCH 09/33] Fixed regex default argument for --space-mapping
 option.

---
 generate_all_operators.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index b9b7d1c..32d9671 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -120,7 +120,7 @@ def parse_arguments():
     parser.add_argument(
         "-s",
         "--space-mapping",
-        default="",
+        default=".*?",
         help="Filter by function space, e.g. 'P1' or 'P1toP2' (regex).",
         # TODO add choices list and type=string.lower
     )
-- 
GitLab


From c24f1a3b545d227fb7e0442b6b2c37a41b4b2261 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 11:53:31 +0200
Subject: [PATCH 10/33] Ignoring case in re.fullmatch.

---
 generate_all_operators.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 32d9671..ee34f56 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -357,8 +357,8 @@ def main():
 
     filtered_operators = list(
         filter(
-            lambda o: re.fullmatch(args.space_mapping, o.mapping)
-            and re.fullmatch(args.form, o.name),
+            lambda o: re.fullmatch(args.space_mapping, o.mapping, re.IGNORECASE)
+            and re.fullmatch(args.form, o.name, re.IGNORECASE),
             operators,
         )
     )
-- 
GitLab


From ad2923f5c83775c10a26792310cf954628c98750 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 11:54:10 +0200
Subject: [PATCH 11/33] HyTeG integration tests: Fixing space mappings. Added
 P2Vector epsilon test.

---
 hyteg_integration_tests/src/CMakeLists.txt    |  10 +-
 hyteg_integration_tests/src/EpsilonVector.cpp | 111 ++++++++++++++++++
 2 files changed, 117 insertions(+), 4 deletions(-)
 create mode 100644 hyteg_integration_tests/src/EpsilonVector.cpp

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index b84602b..40a39cc 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -54,10 +54,12 @@ add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts
 add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
 add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR IQT    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS QUADLOOPS TABULATE )
 
-add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
-add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
-add_operator_test(FILE Epsilon.cpp DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
-add_operator_test(FILE Epsilon.cpp DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
+add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2ToP1 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
+add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P1ToP2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
+
+add_operator_test(FILE Epsilon.cpp       DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
+add_operator_test(FILE Epsilon.cpp       DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
+add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
 
 # tests with blending
 
diff --git a/hyteg_integration_tests/src/EpsilonVector.cpp b/hyteg_integration_tests/src/EpsilonVector.cpp
new file mode 100644
index 0000000..394e9d7
--- /dev/null
+++ b/hyteg_integration_tests/src/EpsilonVector.cpp
@@ -0,0 +1,111 @@
+/*
+ * HyTeG Operator Generator
+ * Copyright (C) 2017-2024  Nils Kohl, Fabian Böhm, Daniel Bauer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <type_traits>
+
+#include "core/DataTypes.h"
+
+#include "hyteg/elementwiseoperators/P2ElementwiseOperator.hpp"
+#include "hyteg/forms/form_hyteg_generated/p2/p2_epsilonvar_affine_q4.hpp"
+#include "hyteg/p2functionspace/P2Function.hpp"
+
+#include "Epsilon/TestOpEpsilon.hpp"
+#include "OperatorGenerationTest.hpp"
+
+using namespace hyteg;
+using walberla::real_t;
+
+constexpr bool only3d = std::is_constructible< FORM, std::function< real_t( const Point3D& ) > >::value;
+
+real_t k( const hyteg::Point3D& x )
+{
+   return x[0] * x[0] - x[2] * x[2] - x[1] * x[1] + 3 * x[0] + x[1] - 5 * x[2] + 2;
+}
+
+P2ElementwiseAffineEpsilonOperator makeRefOp( std::shared_ptr< PrimitiveStorage > storage, uint_t minLevel, uint_t maxLevel )
+{
+   return P2ElementwiseAffineEpsilonOperator( storage, minLevel, maxLevel, k );
+};
+
+template < class Op >
+Op makeTestOp( std::shared_ptr< PrimitiveStorage > storage, uint_t minLevel, uint_t maxLevel )
+{
+   P2Function< real_t > k( "k", storage, minLevel, maxLevel );
+   for ( size_t lvl = minLevel; lvl <= maxLevel; ++lvl )
+   {
+      k.interpolate( ::k, lvl );
+   }
+   return Op( storage, minLevel, maxLevel, k );
+};
+
+int main( int argc, char* argv[] )
+{
+   walberla::MPIManager::instance()->initializeMPI( &argc, &argv );
+   walberla::MPIManager::instance()->useWorldComm();
+
+   const uint_t level = 3;
+
+   real_t thresholdOverMachineEpsApply    = 225;
+   real_t thresholdOverMachineEpsInvDiag  = 9.0e6;
+   real_t thresholdOverMachineEpsAssembly = 360;
+
+   for ( uint_t d = only3d ? 3 : 2; d <= 3; ++d )
+   {
+      StorageSetup storageSetup;
+      if ( d == 2 )
+      {
+         storageSetup = StorageSetup(
+             "quad_4el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/quad_4el.msh" ), GeometryMap::Type::IDENTITY );
+      }
+      else
+      {
+         storageSetup = StorageSetup(
+             "cube_6el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/3D/cube_6el.msh" ), GeometryMap::Type::IDENTITY );
+      }
+
+      compareApply< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+          makeRefOp,
+          makeTestOp< operatorgeneration::TestOpEpsilon >,
+          level,
+          storageSetup,
+          storageSetup.description() + " Apply",
+          thresholdOverMachineEpsApply );
+
+#ifdef TEST_DIAG
+      compareInvDiag< P2Function< real_t >, P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+          makeRefOp,
+          makeTestOp< operatorgeneration::TestOpEpsilon >,
+          level,
+          storageSetup,
+          storageSetup.description() + " Inverse Diagonal",
+          thresholdOverMachineEpsInvDiag );
+#endif
+
+#ifdef TEST_ASSEMBLE
+      compareAssembledMatrix< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+          makeRefOp,
+          makeTestOp< operatorgeneration::TestOpEpsilon >,
+          level,
+          storageSetup,
+          storageSetup.description() + " Assembly",
+          thresholdOverMachineEpsAssembly );
+#endif
+   }
+
+   return EXIT_SUCCESS;
+}
-- 
GitLab


From bafe845030e8d8a6d9c0ce936665fee158db210b Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 12:11:28 +0200
Subject: [PATCH 12/33] HyTeG integration tests: Fixing DiffusionP1Vector.cpp.

---
 hyteg_integration_tests/src/DiffusionP1Vector.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hyteg_integration_tests/src/DiffusionP1Vector.cpp b/hyteg_integration_tests/src/DiffusionP1Vector.cpp
index 5c3424b..2cbd2c9 100644
--- a/hyteg_integration_tests/src/DiffusionP1Vector.cpp
+++ b/hyteg_integration_tests/src/DiffusionP1Vector.cpp
@@ -52,7 +52,7 @@ int main( int argc, char* argv[] )
           level, storageSetup, 225, 9.0e6 );
 
 #ifdef TEST_ASSEMBLE
-      compareAssembledMatrix< P1ElementwiseLaplaceOperator, operatorgeneration::TestOpDiffusion >(
+      compareAssembledMatrix< P1ElementwiseVectorLaplaceOperator, operatorgeneration::TestOpDiffusion >(
           level, storageSetup, storageSetup.description() + " Assembly", 360 );
 #endif
    }
-- 
GitLab


From 175ab6ea7edac101876b153ad1c966785899d5bf Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 12:58:13 +0200
Subject: [PATCH 13/33] HyTeG integration tests: Fixing max magnitude check for
 vector functions.

---
 .../src/OperatorGenerationTest.hpp            | 30 +++++++++++++------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/hyteg_integration_tests/src/OperatorGenerationTest.hpp b/hyteg_integration_tests/src/OperatorGenerationTest.hpp
index 8e4e6ca..abae369 100644
--- a/hyteg_integration_tests/src/OperatorGenerationTest.hpp
+++ b/hyteg_integration_tests/src/OperatorGenerationTest.hpp
@@ -218,13 +218,17 @@ void compareApply( OperatorFactory< RefOpType > refOpFactory,
    }
 
    real_t maxAbs = 1.0;
-   if constexpr ( !std::is_same< RefDstFncType, n1e1::N1E1VectorFunction< RefType > >::value )
+   if constexpr ( std::is_same< RefDstFncType, n1e1::N1E1VectorFunction< RefType > >::value )
    {
-      maxAbs = err.getMaxMagnitude( level );
+      maxAbs = err.getDoFs()->getMaxMagnitude( level );
+   }
+   else if constexpr ( std::is_same_v< RefDstFncType, P1VectorFunction< RefType > > || std::is_same_v< RefDstFncType, P2VectorFunction< RefType > > )
+   {
+      maxAbs = err.getMaxComponentMagnitude( level, All );
    }
    else
    {
-      maxAbs = err.getDoFs()->getMaxMagnitude( level );
+      maxAbs = err.getMaxMagnitude( level );
    }
 
    const real_t threshold = thresholdOverMachineEps * precisionDict::epsilon< TestType >();
@@ -300,13 +304,17 @@ void compareGEMV( const uint_t        level,
    // compare
    err.assign( { 1.0, -1.0 }, { dst, refDst }, level, All );
    real_t maxAbs = 1.0;
-   if constexpr ( !std::is_same< DstFncType, n1e1::N1E1VectorFunction< real_t > >::value )
+   if constexpr ( std::is_same< DstFncType, n1e1::N1E1VectorFunction< real_t > >::value )
    {
-      maxAbs = err.getMaxMagnitude( level );
+      maxAbs = err.getDoFs()->getMaxMagnitude( level );
+   }
+   else if constexpr ( std::is_same_v< RefDstFncType, P1VectorFunction< real_t > > || std::is_same_v< RefDstFncType, P2VectorFunction< real_t > > )
+   {
+      maxAbs = err.getMaxComponentMagnitude( level, All );
    }
    else
    {
-      maxAbs = err.getDoFs()->getMaxMagnitude( level );
+      maxAbs = err.getMaxMagnitude( level );
    }
 
    const real_t threshold = thresholdOverMachineEps * precisionDict::epsilon< TestType >();
@@ -381,13 +389,17 @@ void compareInvDiag( OperatorFactory< RefOpType >  refOpFactory,
    }
 
    real_t maxAbs = 1.0;
-   if constexpr ( !std::is_same< RefDiagType, n1e1::N1E1VectorFunction< RefType > >::value )
+   if constexpr ( std::is_same< RefDiagType, n1e1::N1E1VectorFunction< RefType > >::value )
    {
-      maxAbs = err.getMaxMagnitude( level );
+      maxAbs = err.getDoFs()->getMaxMagnitude( level );
+   }
+   else if constexpr ( std::is_same_v< RefDiagType, P1VectorFunction< RefType > > || std::is_same_v< RefDiagType, P2VectorFunction< RefType > > )
+   {
+      maxAbs = err.getMaxComponentMagnitude( level, All );
    }
    else
    {
-      maxAbs = err.getDoFs()->getMaxMagnitude( level );
+      maxAbs = err.getMaxMagnitude( level );
    }
 
    const real_t threshold = thresholdOverMachineEps * precisionDict::epsilon< TestType >();
-- 
GitLab


From 752a12f651cf2f155fefc1a80be7e6d4eba2f02d Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 13:22:00 +0200
Subject: [PATCH 14/33] HyTeG integration tests: Minor test fix.

---
 hyteg_integration_tests/src/OperatorGenerationTest.hpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hyteg_integration_tests/src/OperatorGenerationTest.hpp b/hyteg_integration_tests/src/OperatorGenerationTest.hpp
index abae369..3eaec8c 100644
--- a/hyteg_integration_tests/src/OperatorGenerationTest.hpp
+++ b/hyteg_integration_tests/src/OperatorGenerationTest.hpp
@@ -308,7 +308,7 @@ void compareGEMV( const uint_t        level,
    {
       maxAbs = err.getDoFs()->getMaxMagnitude( level );
    }
-   else if constexpr ( std::is_same_v< RefDstFncType, P1VectorFunction< real_t > > || std::is_same_v< RefDstFncType, P2VectorFunction< real_t > > )
+   else if constexpr ( std::is_same_v< DstFncType, P1VectorFunction< real_t > > || std::is_same_v< DstFncType, P2VectorFunction< real_t > > )
    {
       maxAbs = err.getMaxComponentMagnitude( level, All );
    }
-- 
GitLab


From 0d385ac28db9f74cbb618adf0b68a7eec85f9a96 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 13:50:28 +0200
Subject: [PATCH 15/33] HyTeG integration tests: Trying to fix test builds by
 linking mixed_operator lib.

---
 hyteg_integration_tests/src/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 40a39cc..8ab65f8 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -48,7 +48,7 @@ add_operator_test(FILE DiffusionP1.cpp       DEF TEST_ASSEMBLE FORM Diffusion AB
 add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1VIP    GEN_ARGS -s P1       --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE POLYCSE)
 add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1CVI    GEN_ARGS -s P1       --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE)
 add_operator_test(FILE DiffusionP2.cpp       DEF TEST_ASSEMBLE FORM Diffusion ABBR 2       GEN_ARGS -s P2       --quad-degree 2 2)
-add_operator_test(FILE DiffusionP1Vector.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1Vec    GEN_ARGS -s P1Vector --quad-degree 0 0)
+add_operator_test(FILE DiffusionP1Vector.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1Vec    GEN_ARGS -s P1Vector --quad-degree 0 0                                                  ARG_LIBS mixed_operator)
 
 add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts GEN_ARGS -s P2 --quad-degree 4 4)
 add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
-- 
GitLab


From a7e0df987e6958cf162f00f35d01dc90d1d16d90 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 13:54:10 +0200
Subject: [PATCH 16/33] HyTeG integration tests: Trying to fix test builds by
 linking mixed_operator lib part II

---
 hyteg_integration_tests/src/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 8ab65f8..fb37499 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -48,7 +48,7 @@ add_operator_test(FILE DiffusionP1.cpp       DEF TEST_ASSEMBLE FORM Diffusion AB
 add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1VIP    GEN_ARGS -s P1       --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE POLYCSE)
 add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1CVI    GEN_ARGS -s P1       --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE)
 add_operator_test(FILE DiffusionP2.cpp       DEF TEST_ASSEMBLE FORM Diffusion ABBR 2       GEN_ARGS -s P2       --quad-degree 2 2)
-add_operator_test(FILE DiffusionP1Vector.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1Vec    GEN_ARGS -s P1Vector --quad-degree 0 0                                                  ARG_LIBS mixed_operator)
+add_operator_test(FILE DiffusionP1Vector.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1Vec    GEN_ARGS -s P1Vector --quad-degree 0 0                                                  LIBS mixed_operator)
 
 add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts GEN_ARGS -s P2 --quad-degree 4 4)
 add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
-- 
GitLab


From a270737fe11ca32d825341fe99d6677aa8168ada Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 16 May 2024 17:11:44 +0200
Subject: [PATCH 17/33] Adding P2Vector epsilon op to available operators.

---
 generate_all_operators.py | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index ee34f56..280a298 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -527,6 +527,7 @@ def all_operators(
     P1 = LagrangianFunctionSpace(1, symbolizer)
     P1Vector = TensorialVectorFunctionSpace(P1)
     P2 = LagrangianFunctionSpace(2, symbolizer)
+    P2Vector = TensorialVectorFunctionSpace(P2)
     N1E1 = N1E1Space(symbolizer)
 
     two_d = [TriangleElement()]
@@ -571,6 +572,24 @@ def all_operators(
 
     # fmt: on
 
+    p2vec_epsilon = partial(
+        epsilon,
+        variable_viscosity=True,
+        coefficient_function_space=P2,
+    )
+    ops.append(
+        OperatorInfo(
+            mapping=f"P2Vector",
+            name=f"Epsilon",
+            trial_space=P2Vector,
+            test_space=P2Vector,
+            form=p2vec_epsilon,
+            type_descriptor=type_descriptor,
+            opts=opts,
+            blending=blending,
+        )
+    )
+
     for c in [0, 1, 2]:
         # fmt: off
         div_geometries = [TriangleElement(), TetrahedronElement()]
-- 
GitLab


From 7126768dbbd310b13a63957192d6e11ca0650e2c Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Mon, 27 May 2024 14:13:50 +0200
Subject: [PATCH 18/33] Adding option to only generate 2D or 3D part in script.
 Then removed VectorEpsilon test in 3D since it generates very long in CI.

---
 generate_all_operators.py                     | 19 ++++++
 hyteg_integration_tests/src/CMakeLists.txt    |  6 +-
 hyteg_integration_tests/src/EpsilonVector.cpp | 66 ++++++++-----------
 3 files changed, 50 insertions(+), 41 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 280a298..83e1791 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -198,6 +198,14 @@ def parse_arguments():
         help=f"Specify the name of the generated C++ class.",
     )
 
+    parser.add_argument(
+        "--dimensions",
+        type=int,
+        nargs="+",
+        default=[2, 3],
+        help=f"Domain dimensions for which operators shall be generated.",
+    )
+
     logging_group = parser.add_mutually_exclusive_group(required=False)
     logging_group.add_argument(
         "-v",
@@ -363,6 +371,17 @@ def main():
         )
     )
 
+    enabled_geometries = set()
+    if 2 in args.dimensions:
+        enabled_geometries.add(TriangleElement())
+    if 3 in args.dimensions:
+        enabled_geometries.add(TetrahedronElement())
+
+    for f in filtered_operators:
+        f.geometries = list(set(f.geometries) & enabled_geometries)
+
+    filtered_operators = [f for f in filtered_operators if f.geometries]
+
     if len(filtered_operators) == 0:
         raise HOGException(
             f"Form {args.form} does not match any available forms. Run --list for a list."
diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index fb37499..76a9e24 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -57,9 +57,9 @@ add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR IQT
 add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2ToP1 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
 add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P1ToP2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
 
-add_operator_test(FILE Epsilon.cpp       DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
-add_operator_test(FILE Epsilon.cpp       DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
-add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
+add_operator_test(FILE Epsilon.cpp       DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
+add_operator_test(FILE Epsilon.cpp       DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
+add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4 --dimensions 2)
 
 # tests with blending
 
diff --git a/hyteg_integration_tests/src/EpsilonVector.cpp b/hyteg_integration_tests/src/EpsilonVector.cpp
index 394e9d7..a6c99bd 100644
--- a/hyteg_integration_tests/src/EpsilonVector.cpp
+++ b/hyteg_integration_tests/src/EpsilonVector.cpp
@@ -30,8 +30,6 @@
 using namespace hyteg;
 using walberla::real_t;
 
-constexpr bool only3d = std::is_constructible< FORM, std::function< real_t( const Point3D& ) > >::value;
-
 real_t k( const hyteg::Point3D& x )
 {
    return x[0] * x[0] - x[2] * x[2] - x[1] * x[1] + 3 * x[0] + x[1] - 5 * x[2] + 2;
@@ -64,48 +62,40 @@ int main( int argc, char* argv[] )
    real_t thresholdOverMachineEpsInvDiag  = 9.0e6;
    real_t thresholdOverMachineEpsAssembly = 360;
 
-   for ( uint_t d = only3d ? 3 : 2; d <= 3; ++d )
-   {
-      StorageSetup storageSetup;
-      if ( d == 2 )
-      {
-         storageSetup = StorageSetup(
-             "quad_4el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/quad_4el.msh" ), GeometryMap::Type::IDENTITY );
-      }
-      else
-      {
-         storageSetup = StorageSetup(
-             "cube_6el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/3D/cube_6el.msh" ), GeometryMap::Type::IDENTITY );
-      }
-
-      compareApply< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
-          makeRefOp,
-          makeTestOp< operatorgeneration::TestOpEpsilon >,
-          level,
-          storageSetup,
-          storageSetup.description() + " Apply",
-          thresholdOverMachineEpsApply );
+   // Testing only 2D since generation of 3D operator just takes too long.
+
+   StorageSetup storageSetup;
+   storageSetup = StorageSetup(
+      "quad_4el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/quad_4el.msh" ), GeometryMap::Type::IDENTITY );
+   }
+
+   compareApply< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+       makeRefOp,
+       makeTestOp< operatorgeneration::TestOpEpsilon >,
+       level,
+       storageSetup,
+       storageSetup.description() + " Apply",
+       thresholdOverMachineEpsApply );
 
 #ifdef TEST_DIAG
-      compareInvDiag< P2Function< real_t >, P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
-          makeRefOp,
-          makeTestOp< operatorgeneration::TestOpEpsilon >,
-          level,
-          storageSetup,
-          storageSetup.description() + " Inverse Diagonal",
-          thresholdOverMachineEpsInvDiag );
+   compareInvDiag< P2Function< real_t >, P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+       makeRefOp,
+       makeTestOp< operatorgeneration::TestOpEpsilon >,
+       level,
+       storageSetup,
+       storageSetup.description() + " Inverse Diagonal",
+       thresholdOverMachineEpsInvDiag );
 #endif
 
 #ifdef TEST_ASSEMBLE
-      compareAssembledMatrix< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
-          makeRefOp,
-          makeTestOp< operatorgeneration::TestOpEpsilon >,
-          level,
-          storageSetup,
-          storageSetup.description() + " Assembly",
-          thresholdOverMachineEpsAssembly );
+   compareAssembledMatrix< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+       makeRefOp,
+       makeTestOp< operatorgeneration::TestOpEpsilon >,
+       level,
+       storageSetup,
+       storageSetup.description() + " Assembly",
+       thresholdOverMachineEpsAssembly );
 #endif
-   }
 
    return EXIT_SUCCESS;
 }
-- 
GitLab


From e1d588f155469a0b66ed2292af186aca0aab634b Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Mon, 27 May 2024 14:22:38 +0200
Subject: [PATCH 19/33] Trying to satisfy the mypy type checker for the
 dimensions parameter in the operator generation script.

---
 generate_all_operators.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 83e1791..ae1c2f2 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -371,7 +371,7 @@ def main():
         )
     )
 
-    enabled_geometries = set()
+    enabled_geometries: Set[TriangleElement | TetrahedronElement] = set()
     if 2 in args.dimensions:
         enabled_geometries.add(TriangleElement())
     if 3 in args.dimensions:
-- 
GitLab


From 9d6bf5c0148e05a62d595317de98d40e4870ef73 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Mon, 27 May 2024 15:19:32 +0200
Subject: [PATCH 20/33] Fixing epsilon vector test.

---
 hyteg_integration_tests/src/CMakeLists.txt    | 2 +-
 hyteg_integration_tests/src/EpsilonVector.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 76a9e24..c84aea7 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -59,7 +59,7 @@ add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM D
 
 add_operator_test(FILE Epsilon.cpp       DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
 add_operator_test(FILE Epsilon.cpp       DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
-add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4 --dimensions 2)
+add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4 --dimensions 2 LIBS constant_stencil_operator)
 
 # tests with blending
 
diff --git a/hyteg_integration_tests/src/EpsilonVector.cpp b/hyteg_integration_tests/src/EpsilonVector.cpp
index a6c99bd..581de51 100644
--- a/hyteg_integration_tests/src/EpsilonVector.cpp
+++ b/hyteg_integration_tests/src/EpsilonVector.cpp
@@ -21,6 +21,7 @@
 #include "core/DataTypes.h"
 
 #include "hyteg/elementwiseoperators/P2ElementwiseOperator.hpp"
+#include "hyteg/src/constant_stencil_operator/P2ConstantEpsilonOperator.hpp"
 #include "hyteg/forms/form_hyteg_generated/p2/p2_epsilonvar_affine_q4.hpp"
 #include "hyteg/p2functionspace/P2Function.hpp"
 
@@ -67,7 +68,6 @@ int main( int argc, char* argv[] )
    StorageSetup storageSetup;
    storageSetup = StorageSetup(
       "quad_4el", MeshInfo::fromGmshFile( "../hyteg/data/meshes/quad_4el.msh" ), GeometryMap::Type::IDENTITY );
-   }
 
    compareApply< P2ElementwiseAffineEpsilonOperator, operatorgeneration::TestOpEpsilon >(
        makeRefOp,
-- 
GitLab


From 53b7711f646494098caf2b3cd90de08b46235c46 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Mon, 27 May 2024 17:24:48 +0200
Subject: [PATCH 21/33] Fixing epsilon vector test pt. II (wrong include path).

---
 hyteg_integration_tests/src/EpsilonVector.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hyteg_integration_tests/src/EpsilonVector.cpp b/hyteg_integration_tests/src/EpsilonVector.cpp
index 581de51..5ad41ed 100644
--- a/hyteg_integration_tests/src/EpsilonVector.cpp
+++ b/hyteg_integration_tests/src/EpsilonVector.cpp
@@ -21,7 +21,7 @@
 #include "core/DataTypes.h"
 
 #include "hyteg/elementwiseoperators/P2ElementwiseOperator.hpp"
-#include "hyteg/src/constant_stencil_operator/P2ConstantEpsilonOperator.hpp"
+#include "constant_stencil_operator/P2ConstantEpsilonOperator.hpp"
 #include "hyteg/forms/form_hyteg_generated/p2/p2_epsilonvar_affine_q4.hpp"
 #include "hyteg/p2functionspace/P2Function.hpp"
 
-- 
GitLab


From e84da78aaf5459865e132ec43fc20d2c95788dd8 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 28 May 2024 13:55:11 +0200
Subject: [PATCH 22/33] Passing correct geometries already in the operator info
 in generate_all_operators.py.

---
 generate_all_operators.py | 51 +++++++++++++++++++--------------------
 1 file changed, 25 insertions(+), 26 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index ae1c2f2..36a1b66 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -353,16 +353,21 @@ def main():
             args.quad_degree if args.quad_rule is None else args.quad_rule,
         )
     }
+
+    enabled_geometries: Set[TriangleElement | TetrahedronElement] = set()
+    if 2 in args.dimensions:
+        enabled_geometries.add(TriangleElement())
+    if 3 in args.dimensions:
+        enabled_geometries.add(TetrahedronElement())
+
     operators = all_operators(
         symbolizer,
         [(opts, loop_strategy, ordered_opts_suffix(loop_strategy, opts))],
         type_descriptor,
         blending,
+        geometries=enabled_geometries,
     )
 
-    re_mapping = re.compile(args.space_mapping)
-    re_form = re.compile(args.form)
-
     filtered_operators = list(
         filter(
             lambda o: re.fullmatch(args.space_mapping, o.mapping, re.IGNORECASE)
@@ -371,15 +376,6 @@ def main():
         )
     )
 
-    enabled_geometries: Set[TriangleElement | TetrahedronElement] = set()
-    if 2 in args.dimensions:
-        enabled_geometries.add(TriangleElement())
-    if 3 in args.dimensions:
-        enabled_geometries.add(TetrahedronElement())
-
-    for f in filtered_operators:
-        f.geometries = list(set(f.geometries) & enabled_geometries)
-
     filtered_operators = [f for f in filtered_operators if f.geometries]
 
     if len(filtered_operators) == 0:
@@ -542,6 +538,7 @@ def all_operators(
     opts: List[Tuple[Set[Opts], LoopStrategy, str]],
     type_descriptor: HOGType,
     blending: GeometryMap,
+    geometries: Set[ElementGeometry],
 ) -> List[OperatorInfo]:
     P1 = LagrangianFunctionSpace(1, symbolizer)
     P1Vector = TensorialVectorFunctionSpace(P1)
@@ -549,8 +546,8 @@ def all_operators(
     P2Vector = TensorialVectorFunctionSpace(P2)
     N1E1 = N1E1Space(symbolizer)
 
-    two_d = [TriangleElement()]
-    three_d = [TetrahedronElement()]
+    two_d = list({TriangleElement()} | geometries)
+    three_d = list({TetrahedronElement()} | geometries)
 
     ops: List[OperatorInfo] = []
 
@@ -564,30 +561,31 @@ def all_operators(
                             form=partial(curl_curl_plus_mass, alpha_fem_space=P1, beta_fem_space=P1),
                             type_descriptor=type_descriptor, geometries=three_d, opts=opts, blending=blending))
     ops.append(OperatorInfo(mapping="P1", name="Diffusion", trial_space=P1, test_space=P1, form=diffusion,
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
     ops.append(OperatorInfo(mapping="P1", name="DivKGrad", trial_space=P1, test_space=P1,
                             form=partial(div_k_grad, coefficient_function_space=P1),
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
 
     ops.append(OperatorInfo(mapping="P2", name="Diffusion", trial_space=P2, test_space=P2, form=diffusion,
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
     ops.append(OperatorInfo(mapping="P2", name="DivKGrad", trial_space=P2, test_space=P2,
                             form=partial(div_k_grad, coefficient_function_space=P2),
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
+
     ops.append(OperatorInfo(mapping="P2", name="ShearHeating", trial_space=P2, test_space=P2,
                             form=partial(shear_heating, viscosity_function_space=P2, velocity_function_space=P2),
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
 
     ops.append(OperatorInfo(mapping="P1", name="NonlinearDiffusion", trial_space=P1, test_space=P1,
                             form=partial(nonlinear_diffusion, coefficient_function_space=P1),
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
     ops.append(OperatorInfo(mapping="P1", name="NonlinearDiffusionNewtonGalerkin", trial_space=P1,
                             test_space=P1, form=partial(nonlinear_diffusion_newton_galerkin,
                             coefficient_function_space=P1, onlyNewtonGalerkinPartOfForm=False),
-                            type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
 
     ops.append(OperatorInfo(mapping="P1Vector", name="Diffusion", trial_space=P1Vector, test_space=P1Vector,
-                            form=diffusion, type_descriptor=type_descriptor, opts=opts, blending=blending))
+                            form=diffusion, type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
 
     # fmt: on
 
@@ -604,6 +602,7 @@ def all_operators(
             test_space=P2Vector,
             form=p2vec_epsilon,
             type_descriptor=type_descriptor,
+            geometries=list(geometries),
             opts=opts,
             blending=blending,
         )
@@ -611,9 +610,9 @@ def all_operators(
 
     for c in [0, 1, 2]:
         # fmt: off
-        div_geometries = [TriangleElement(), TetrahedronElement()]
+        div_geometries = geometries
         if c == 2:
-            div_geometries = [TetrahedronElement()]
+            div_geometries = three_d
         ops.append(OperatorInfo(mapping=f"P2ToP1", name=f"Div_{c}", trial_space=P1, test_space=P2,
                                 form=partial(divergence, transpose=False, component_index=c),
                                 type_descriptor=type_descriptor, opts=opts, geometries=div_geometries,
@@ -644,9 +643,9 @@ def all_operators(
             # fmt: off
             ops.append(
                 OperatorInfo(mapping=f"P2", name=f"Epsilon_{r}_{c}", trial_space=P2, test_space=P2, form=p2_epsilon,
-                              type_descriptor=type_descriptor, opts=opts, blending=blending))
+                              type_descriptor=type_descriptor, geometries=list(geometries), opts=opts, blending=blending))
             ops.append(OperatorInfo(mapping=f"P2", name=f"FullStokes_{r}_{c}", trial_space=P2, test_space=P2,
-                                    form=p2_full_stokes, type_descriptor=type_descriptor, opts=opts,
+                                    form=p2_full_stokes, type_descriptor=type_descriptor, geometries=list(geometries), opts=opts,
                                     blending=blending))
             # fmt: on
     for c, r in [(0, 2), (1, 2), (2, 2), (2, 1), (2, 0)]:
-- 
GitLab


From 26bb27787180b70e7e530d722f6565c2be1c8e08 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 28 May 2024 14:03:00 +0200
Subject: [PATCH 23/33] Typing issue in generate_all_operators.py.

---
 generate_all_operators.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 36a1b66..a2539bc 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -610,7 +610,7 @@ def all_operators(
 
     for c in [0, 1, 2]:
         # fmt: off
-        div_geometries = geometries
+        div_geometries = list(geometries)
         if c == 2:
             div_geometries = three_d
         ops.append(OperatorInfo(mapping=f"P2ToP1", name=f"Div_{c}", trial_space=P1, test_space=P2,
-- 
GitLab


From 141eca525da85e88a4463be718a0e8bed78ebcfa Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 28 May 2024 14:09:45 +0200
Subject: [PATCH 24/33] Typing issue II in generate_all_operators.py.

---
 generate_all_operators.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index a2539bc..6fda1e5 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -22,7 +22,7 @@ from functools import partial
 import logging
 import os
 import re
-from typing import Callable, List, Optional, Sequence, Set, Tuple
+from typing import Callable, List, Optional, Sequence, Set, Tuple, Union
 
 import numpy as np
 import sympy as sp
@@ -538,7 +538,7 @@ def all_operators(
     opts: List[Tuple[Set[Opts], LoopStrategy, str]],
     type_descriptor: HOGType,
     blending: GeometryMap,
-    geometries: Set[ElementGeometry],
+    geometries: Set[Union[TriangleElement, TetrahedronElement]],
 ) -> List[OperatorInfo]:
     P1 = LagrangianFunctionSpace(1, symbolizer)
     P1Vector = TensorialVectorFunctionSpace(P1)
-- 
GitLab


From ba283efac2f848e82d4c22c68b5c9a1772c685f2 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 28 May 2024 15:31:21 +0200
Subject: [PATCH 25/33] Fixed set intersection in generate_all_operators.py.

---
 generate_all_operators.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 6fda1e5..eb5fd23 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -546,8 +546,8 @@ def all_operators(
     P2Vector = TensorialVectorFunctionSpace(P2)
     N1E1 = N1E1Space(symbolizer)
 
-    two_d = list({TriangleElement()} | geometries)
-    three_d = list({TetrahedronElement()} | geometries)
+    two_d = list({TriangleElement()} & geometries)
+    three_d = list({TetrahedronElement()} & geometries)
 
     ops: List[OperatorInfo] = []
 
-- 
GitLab


From ad544078e4ce80f3f00639c14df60cd260a06901 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 28 May 2024 15:48:22 +0200
Subject: [PATCH 26/33] Another type fix.

mypy is driving me insane.
---
 generate_all_operators.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index eb5fd23..9113125 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -610,9 +610,10 @@ def all_operators(
 
     for c in [0, 1, 2]:
         # fmt: off
-        div_geometries = list(geometries)
         if c == 2:
             div_geometries = three_d
+        else:
+            div_geometries = list(geometries)
         ops.append(OperatorInfo(mapping=f"P2ToP1", name=f"Div_{c}", trial_space=P1, test_space=P2,
                                 form=partial(divergence, transpose=False, component_index=c),
                                 type_descriptor=type_descriptor, opts=opts, geometries=div_geometries,
-- 
GitLab


From 6133ddb2dd42738150a4dccc64b013d2a93b8815 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Tue, 28 May 2024 16:11:28 +0200
Subject: [PATCH 27/33] Another type fix (II).

mypy is driving me insane.
---
 generate_all_operators.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index 9113125..ea28b40 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -538,7 +538,7 @@ def all_operators(
     opts: List[Tuple[Set[Opts], LoopStrategy, str]],
     type_descriptor: HOGType,
     blending: GeometryMap,
-    geometries: Set[Union[TriangleElement, TetrahedronElement]],
+    geometries: Set,
 ) -> List[OperatorInfo]:
     P1 = LagrangianFunctionSpace(1, symbolizer)
     P1Vector = TensorialVectorFunctionSpace(P1)
-- 
GitLab


From 4e303f6e24d9f9c785ec28f8e5bd161bc9356008 Mon Sep 17 00:00:00 2001
From: Daniel Bauer <daniel.j.bauer@fau.de>
Date: Wed, 29 May 2024 11:01:49 +0200
Subject: [PATCH 28/33] fix typing

---
 generate_all_operators.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/generate_all_operators.py b/generate_all_operators.py
index ea28b40..4159e66 100644
--- a/generate_all_operators.py
+++ b/generate_all_operators.py
@@ -538,7 +538,7 @@ def all_operators(
     opts: List[Tuple[Set[Opts], LoopStrategy, str]],
     type_descriptor: HOGType,
     blending: GeometryMap,
-    geometries: Set,
+    geometries: Set[Union[TriangleElement, TetrahedronElement]],
 ) -> List[OperatorInfo]:
     P1 = LagrangianFunctionSpace(1, symbolizer)
     P1Vector = TensorialVectorFunctionSpace(P1)
@@ -546,8 +546,8 @@ def all_operators(
     P2Vector = TensorialVectorFunctionSpace(P2)
     N1E1 = N1E1Space(symbolizer)
 
-    two_d = list({TriangleElement()} & geometries)
-    three_d = list({TetrahedronElement()} & geometries)
+    two_d = list(geometries & {TriangleElement()})
+    three_d = list(geometries & {TetrahedronElement()})
 
     ops: List[OperatorInfo] = []
 
-- 
GitLab


From 69b2f873901beb3fb288263884c558790aa06217 Mon Sep 17 00:00:00 2001
From: Daniel Bauer <daniel.j.bauer@fau.de>
Date: Wed, 29 May 2024 11:14:23 +0200
Subject: [PATCH 29/33] fix inverse diagonal kernel

---
 hog/operator_generation/kernel_types.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/hog/operator_generation/kernel_types.py b/hog/operator_generation/kernel_types.py
index c2c781a..778077c 100644
--- a/hog/operator_generation/kernel_types.py
+++ b/hog/operator_generation/kernel_types.py
@@ -396,6 +396,7 @@ class AssembleDiagonal(KernelType):
             f'        this->timingTree_->stop( "pre-communication" );\n'
             f"\n"
             f"{indent(macro_loop(3), 2 * INDENT)}\n"
+            f"{indent(self.dst.invert_elementwise(3), 2 * INDENT)}\n"
             f"    }}\n"
             f"    else\n"
             f"    {{\n"
@@ -404,9 +405,9 @@ class AssembleDiagonal(KernelType):
             f'        this->timingTree_->stop( "pre-communication" );\n'
             f"\n"
             f"{indent(macro_loop(2), 2 * INDENT)}\n"
+            f"{indent(self.dst.invert_elementwise(2), 2 * INDENT)}\n"
             f"    }}\n"
             f"\n"
-            f"{indent(self.dst.invert_elementwise(dim=0), INDENT)}\n"
             f"}}\n"
             f"\n"
             f'this->stopTiming( "{self.name}" );'
-- 
GitLab


From e63f998a404c134f4908419ba34731b20d874edc Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 30 May 2024 13:19:35 +0200
Subject: [PATCH 30/33] Lowering quadrature degree of vector epsilon operator
 in HyTeG integration test to match that of the reference operator. Otherwise,
 the comparison tests do not pass.

---
 hyteg_integration_tests/src/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index c84aea7..2f35d52 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -59,7 +59,7 @@ add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM D
 
 add_operator_test(FILE Epsilon.cpp       DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
 add_operator_test(FILE Epsilon.cpp       DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
-add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4 --dimensions 2 LIBS constant_stencil_operator)
+add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2 --dimensions 2 LIBS constant_stencil_operator)
 
 # tests with blending
 
-- 
GitLab


From 81c8b53c32a3f7ecc241ae4bb9e28b8dc2036c2d Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Thu, 30 May 2024 16:34:13 +0200
Subject: [PATCH 31/33] Added a dedicated P2Vector epsilon op blending test in
 2D (testing combination vectorspace + blending).

---
 hyteg_integration_tests/src/CMakeLists.txt    |  33 +-----
 .../src/EpsilonVectorAnnulus.cpp              | 106 ++++++++++++++++++
 2 files changed, 108 insertions(+), 31 deletions(-)
 create mode 100644 hyteg_integration_tests/src/EpsilonVectorAnnulus.cpp

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 2f35d52..03c3b8f 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -37,35 +37,6 @@ endfunction()
 
 # tests without blending
 
-add_operator_test(FILE CurlCurl.cpp DEF TEST_ASSEMBLE FORM CurlCurl ABBR noopts GEN_ARGS --quad-degree 0 0)
-add_operator_test(FILE CurlCurl.cpp                   FORM CurlCurl ABBR VIQ    GEN_ARGS --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE QUADLOOPS)
-add_operator_test(FILE CurlCurl.cpp                   FORM CurlCurl ABBR CVIP   GEN_ARGS --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE POLYCSE)
 
-add_operator_test(FILE CurlCurlPlusMass.cpp DEF TEST_ASSEMBLE FORM CurlCurlPlusMass ABBR noopts GEN_ARGS --quad-degree 2 2)
-add_operator_test(FILE CurlCurlPlusMass.cpp                   FORM CurlCurlPlusMass ABBR VI     GEN_ARGS --quad-degree 2 2 -o MOVECONSTANTS VECTORIZE)
-
-add_operator_test(FILE DiffusionP1.cpp       DEF TEST_ASSEMBLE FORM Diffusion ABBR 1       GEN_ARGS -s P1       --quad-degree 0 0)
-add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1VIP    GEN_ARGS -s P1       --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE POLYCSE)
-add_operator_test(FILE DiffusionP1.cpp                         FORM Diffusion ABBR 1CVI    GEN_ARGS -s P1       --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE)
-add_operator_test(FILE DiffusionP2.cpp       DEF TEST_ASSEMBLE FORM Diffusion ABBR 2       GEN_ARGS -s P2       --quad-degree 2 2)
-add_operator_test(FILE DiffusionP1Vector.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1Vec    GEN_ARGS -s P1Vector --quad-degree 0 0                                                  LIBS mixed_operator)
-
-add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts GEN_ARGS -s P2 --quad-degree 4 4)
-add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
-add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR IQT    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS QUADLOOPS TABULATE )
-
-add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2ToP1 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
-add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P1ToP2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
-
-add_operator_test(FILE Epsilon.cpp       DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
-add_operator_test(FILE Epsilon.cpp       DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT  GEN_ARGS -s P2       -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4               )
-add_operator_test(FILE EpsilonVector.cpp DEF           FORM Epsilon                                             ABBR VecVIQT GEN_ARGS -s P2Vector -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2 --dimensions 2 LIBS constant_stencil_operator)
-
-# tests with blending
-
-add_operator_test(FILE DiffusionP1Annulus.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR b2     GEN_ARGS -s P1 -b AnnulusMap --quad-rule hillion_07 yu_3)
-add_operator_test(FILE DiffusionP1Annulus.cpp                   FORM Diffusion ABBR b2VIQT GEN_ARGS -s P1 -b AnnulusMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule hillion_07 yu_3)
-add_operator_test(FILE DiffusionP1IcosahedralShell.cpp          FORM Diffusion ABBR b3VIQT GEN_ARGS -s P1 -b IcosahedralShellMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule hillion_07 yu_3)
-
-add_operator_test(FILE FullStokes.cpp DEF TEST_DIAG FORM=forms::p2_full_stokesvar_0_0_blending_q3 FORM FullStokes_0_0 ABBR 00b3VIQT GEN_ARGS -s P2 -b IcosahedralShellMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule yu_3 yu_3)
-add_operator_test(FILE FullStokes.cpp DEF           FORM=forms::p2_full_stokesvar_2_1_blending_q3 FORM FullStokes_2_1 ABBR 21b3VIQT GEN_ARGS -s P2 -b IcosahedralShellMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule yu_3 yu_3)
+add_operator_test(FILE EpsilonVector.cpp        DEF           FORM Epsilon                                             ABBR VecVIQT  GEN_ARGS -s P2Vector               -o MOVECONSTANTS QUADLOOPS TABULATE  --quad-degree 2 2 --dimensions 2 LIBS constant_stencil_operator)
+add_operator_test(FILE EpsilonVectorAnnulus.cpp DEF           FORM Epsilon                                             ABBR VecbVIQT GEN_ARGS -s P2Vector -b AnnulusMap -o MOVECONSTANTS QUADLOOPS TABULATE  --quad-degree 2 2 --dimensions 2 LIBS constant_stencil_operator)
diff --git a/hyteg_integration_tests/src/EpsilonVectorAnnulus.cpp b/hyteg_integration_tests/src/EpsilonVectorAnnulus.cpp
new file mode 100644
index 0000000..89a053a
--- /dev/null
+++ b/hyteg_integration_tests/src/EpsilonVectorAnnulus.cpp
@@ -0,0 +1,106 @@
+/*
+ * HyTeG Operator Generator
+ * Copyright (C) 2017-2024  Nils Kohl, Fabian Böhm, Daniel Bauer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <type_traits>
+
+#include "core/DataTypes.h"
+
+#include "hyteg/elementwiseoperators/P2ElementwiseOperator.hpp"
+#include "constant_stencil_operator/P2ConstantEpsilonOperator.hpp"
+#include "hyteg/forms/form_hyteg_generated/p2/p2_epsilonvar_affine_q4.hpp"
+#include "hyteg/p2functionspace/P2Function.hpp"
+
+#include "Epsilon/TestOpEpsilon.hpp"
+#include "OperatorGenerationTest.hpp"
+
+using namespace hyteg;
+using walberla::real_t;
+
+real_t k( const hyteg::Point3D& x )
+{
+   // The operator also works with a non-constant viscosity.
+   // However, the reference operator seems to have such a low quadrature order that the difference between the
+   // reference and generated operator is relatively large.
+   // Under refinement that error vanishes, but not quickly enough that we can afford refinement in this (quick) test.
+   // For simplicity, the viscosity is therefore set to 1 here, such that the comparison succeeds (just blending, no var
+   // viscosity). Note that there is a dedicated test with a variable viscosity, but without blending.
+   return 1.0;
+}
+
+P2ElementwiseBlendingEpsilonOperator makeRefOp( std::shared_ptr< PrimitiveStorage > storage, uint_t minLevel, uint_t maxLevel )
+{
+   return P2ElementwiseBlendingEpsilonOperator( storage, minLevel, maxLevel, k );
+};
+
+template < class Op >
+Op makeTestOp( std::shared_ptr< PrimitiveStorage > storage, uint_t minLevel, uint_t maxLevel )
+{
+   P2Function< real_t > k( "k", storage, minLevel, maxLevel );
+   for ( size_t lvl = minLevel; lvl <= maxLevel; ++lvl )
+   {
+      k.interpolate( ::k, lvl );
+   }
+   return Op( storage, minLevel, maxLevel, k );
+};
+
+int main( int argc, char* argv[] )
+{
+   walberla::MPIManager::instance()->initializeMPI( &argc, &argv );
+   walberla::MPIManager::instance()->useWorldComm();
+
+   const uint_t level = 3;
+
+   real_t thresholdOverMachineEpsApply    = 2e3;
+   real_t thresholdOverMachineEpsInvDiag  = 9.0e6;
+   real_t thresholdOverMachineEpsAssembly = 360;
+
+   // Testing only 2D since generation of 3D operator just takes too long.
+
+   StorageSetup storageSetup(
+       "Annulus", MeshInfo::meshAnnulus( 1.0, 2.0, MeshInfo::CRISS, 12, 2 ), GeometryMap::Type::ANNULUS );
+
+   compareApply< P2ElementwiseBlendingEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+       makeRefOp,
+       makeTestOp< operatorgeneration::TestOpEpsilon >,
+       level,
+       storageSetup,
+       storageSetup.description() + " Apply",
+       thresholdOverMachineEpsApply );
+
+#ifdef TEST_DIAG
+   compareInvDiag< P2Function< real_t >, P2ElementwiseBlendingEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+       makeRefOp,
+       makeTestOp< operatorgeneration::TestOpEpsilon >,
+       level,
+       storageSetup,
+       storageSetup.description() + " Inverse Diagonal",
+       thresholdOverMachineEpsInvDiag );
+#endif
+
+#ifdef TEST_ASSEMBLE
+   compareAssembledMatrix< P2ElementwiseBlendingEpsilonOperator, operatorgeneration::TestOpEpsilon >(
+       makeRefOp,
+       makeTestOp< operatorgeneration::TestOpEpsilon >,
+       level,
+       storageSetup,
+       storageSetup.description() + " Assembly",
+       thresholdOverMachineEpsAssembly );
+#endif
+
+   return EXIT_SUCCESS;
+}
-- 
GitLab


From 58f76ba13126bb33b793fa90ed6366098d2db4e6 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Fri, 31 May 2024 09:33:59 +0200
Subject: [PATCH 32/33] Re-added tests I accidentally removed.

---
 hyteg_integration_tests/src/CMakeLists.txt | 30 ++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 03c3b8f..54837bb 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -37,6 +37,36 @@ endfunction()
 
 # tests without blending
 
+add_operator_test(FILE CurlCurl.cpp DEF TEST_ASSEMBLE FORM CurlCurl ABBR noopts GEN_ARGS --quad-degree 0 0)
+add_operator_test(FILE CurlCurl.cpp                   FORM CurlCurl ABBR VIQ    GEN_ARGS --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE QUADLOOPS)
+add_operator_test(FILE CurlCurl.cpp                   FORM CurlCurl ABBR CVIP   GEN_ARGS --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE POLYCSE)
+
+add_operator_test(FILE CurlCurlPlusMass.cpp DEF TEST_ASSEMBLE FORM CurlCurlPlusMass ABBR noopts GEN_ARGS --quad-degree 2 2)
+add_operator_test(FILE CurlCurlPlusMass.cpp                   FORM CurlCurlPlusMass ABBR VI     GEN_ARGS --quad-degree 2 2 -o MOVECONSTANTS VECTORIZE)
+
+add_operator_test(FILE DiffusionP1.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 1    GEN_ARGS -s P1 --quad-degree 0 0)
+add_operator_test(FILE DiffusionP1.cpp                   FORM Diffusion ABBR 1VIP GEN_ARGS -s P1 --quad-degree 0 0 -o MOVECONSTANTS VECTORIZE POLYCSE)
+add_operator_test(FILE DiffusionP1.cpp                   FORM Diffusion ABBR 1CVI GEN_ARGS -s P1 --quad-degree 0 0 --loop-strategy CUBES -o MOVECONSTANTS VECTORIZE)
+add_operator_test(FILE DiffusionP2.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR 2    GEN_ARGS -s P2 --quad-degree 2 2)
+
+add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts GEN_ARGS -s P2 --quad-degree 4 4)
+add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
+add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR IQT    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS QUADLOOPS TABULATE )
+
+add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
+add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
+add_operator_test(FILE Epsilon.cpp DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
+add_operator_test(FILE Epsilon.cpp DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
 
 add_operator_test(FILE EpsilonVector.cpp        DEF           FORM Epsilon                                             ABBR VecVIQT  GEN_ARGS -s P2Vector               -o MOVECONSTANTS QUADLOOPS TABULATE  --quad-degree 2 2 --dimensions 2 LIBS constant_stencil_operator)
 add_operator_test(FILE EpsilonVectorAnnulus.cpp DEF           FORM Epsilon                                             ABBR VecbVIQT GEN_ARGS -s P2Vector -b AnnulusMap -o MOVECONSTANTS QUADLOOPS TABULATE  --quad-degree 2 2 --dimensions 2 LIBS constant_stencil_operator)
+
+# tests with blending
+
+add_operator_test(FILE DiffusionP1Annulus.cpp DEF TEST_ASSEMBLE FORM Diffusion ABBR b2     GEN_ARGS -s P1 -b AnnulusMap --quad-rule hillion_07 yu_3)
+add_operator_test(FILE DiffusionP1Annulus.cpp                   FORM Diffusion ABBR b2VIQT GEN_ARGS -s P1 -b AnnulusMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule hillion_07 yu_3)
+add_operator_test(FILE DiffusionP1IcosahedralShell.cpp          FORM Diffusion ABBR b3VIQT GEN_ARGS -s P1 -b IcosahedralShellMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule hillion_07 yu_3)
+
+add_operator_test(FILE FullStokes.cpp DEF TEST_DIAG FORM=forms::p2_full_stokesvar_0_0_blending_q3 FORM FullStokes_0_0 ABBR 00b3VIQT GEN_ARGS -s P2 -b IcosahedralShellMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule yu_3 yu_3)
+add_operator_test(FILE FullStokes.cpp DEF           FORM=forms::p2_full_stokesvar_2_1_blending_q3 FORM FullStokes_2_1 ABBR 21b3VIQT GEN_ARGS -s P2 -b IcosahedralShellMap -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-rule yu_3 yu_3)
+
-- 
GitLab


From a374af87b717f18fe9cd7cc0f60bae139fe1cd68 Mon Sep 17 00:00:00 2001
From: Nils Kohl <nils.kohl@fau.de>
Date: Fri, 31 May 2024 09:58:18 +0200
Subject: [PATCH 33/33] Fixing function spaces for div and divt.

---
 hyteg_integration_tests/src/CMakeLists.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hyteg_integration_tests/src/CMakeLists.txt b/hyteg_integration_tests/src/CMakeLists.txt
index 54837bb..66b018d 100644
--- a/hyteg_integration_tests/src/CMakeLists.txt
+++ b/hyteg_integration_tests/src/CMakeLists.txt
@@ -53,8 +53,8 @@ add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR noopts
 add_operator_test(FILE DivKGrad.cpp                   FORM DivKGrad ABBR VIQ    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS VECTORIZE QUADLOOPS )
 add_operator_test(FILE DivKGrad.cpp DEF TEST_ASSEMBLE FORM DivKGrad ABBR IQT    GEN_ARGS -s P2 --quad-degree 4 4 -o MOVECONSTANTS QUADLOOPS TABULATE )
 
-add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
-add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
+add_operator_test(FILE Div.cpp  DEF REF_OP=P2ToP1ElementwiseDivxOperator  FORM Div_0  ABBR VIQT GEN_ARGS -s P2ToP1 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
+add_operator_test(FILE DivT.cpp DEF REF_OP=P1ToP2ElementwiseDivTzOperator FORM DivT_2 ABBR VIQT GEN_ARGS -s P1ToP2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 2 2)
 add_operator_test(FILE Epsilon.cpp DEF TEST_DIAG FORM=forms::p2_epsilonvar_0_0_affine_q4 FORM Epsilon_0_0 ABBR 00VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
 add_operator_test(FILE Epsilon.cpp DEF           FORM=forms::p2_epsilonvar_2_1_affine_q4 FORM Epsilon_2_1 ABBR 21VIQT GEN_ARGS -s P2 -o MOVECONSTANTS QUADLOOPS TABULATE VECTORIZE --quad-degree 4 4)
 
-- 
GitLab