diff --git a/CMakeLists.txt b/CMakeLists.txt
index f295708..b385901 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,7 @@ project(meshfields VERSION 1.0.1 LANGUAGES CXX)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
+include(CheckGit.cmake)
option(MeshFields_USE_Cabana "Build with the Cabana storage backend" OFF)
@@ -76,6 +77,9 @@ configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/src/MeshField_Config.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/MeshField_Config.hpp")
+
+CheckGitSetup()
+target_sources(meshfields PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/MeshField_GetHash.cpp)
if(MeshFields_USE_Cabana)
target_link_libraries(meshfields INTERFACE Cabana::Core)
target_compile_definitions(meshfields INTERFACE MESHFIELDS_ENABLE_CABANA)
@@ -212,6 +216,9 @@ meshfields_add_exe(CountIntegrator test/testCountIntegrator.cpp)
meshfields_add_smoke_exe(OmegahTriTests test/testOmegahTri.cpp)
meshfields_add_exe(ExceptionTest test/testExceptions.cpp)
meshfields_add_exe(OmegahTetTest test/testOmegahTet.cpp)
+meshfields_add_exe(IntegratorTest test/testIntegrator.cpp)
+meshfields_add_exe(BoxIntegratorTest test/testBoxIntegrator.cpp)
+
meshfields_add_exe(IntegratorMeasure test/testIntegratorMeasure.cpp)
meshfields_add_exe(Interpolate test/testInterpolate.cpp)
meshfields_add_exe(OmegahThwaitesSprAdapt test/testSprThwaitesAdapt.cpp)
@@ -220,6 +227,7 @@ if(MeshFields_USE_Cabana)
meshfields_add_exe(ControllerPerformance test/testControllerPerformance.cpp)
meshfields_add_smoke_exe(CabanaTests test/testCabana.cpp)
meshfields_add_exe(CabanaControllerLifetimeTest test/testCabanaControllerLifetime.cpp)
+ meshfields_add_exe(IntegratorPerformance test/testIntegratorPerformance.cpp)
smoke_test_func(CabanaTests ./CabanaTests)
test_func(ControllerPerformance ./ControllerPerformance)
test_func(CabanaControllerLifetimeTest ./CabanaControllerLifetimeTest)
@@ -233,6 +241,8 @@ test_func(ElementJacobian1d ./ElementJacobian1d)
test_func(ElementJacobian2d ./ElementJacobian2d)
test_func(ElementJacobian3d ./ElementJacobian3d)
test_func(CountIntegrator ./CountIntegrator)
+test_func(IntegratorTest ./IntegratorTest)
+test_func(BoxIntegratorTest ./BoxIntegratorTest)
smoke_test_func(OmegahTriTests ./OmegahTriTests)
test_func(OmegahTetTest ./OmegahTetTest)
test_func(IntegratorMeasure ./IntegratorMeasure)
@@ -244,7 +254,7 @@ else()
will_fail_test_func(ExceptionTest ./ExceptionTest)
endif()
-meshfields_add_exe(OmegahSprAdapt test/testSprThwaitesAdapt.cpp)
+meshfields_add_exe(OmegahSprAdapt test/testSprThwaitesAdapt.cpp)
test_func(OmegahSprAdapt ./OmegahSprAdapt
${CMAKE_SOURCE_DIR}/meshes/thwaites_basal_effectiveStrain.osh/
thwaitesAdapted #output prefix
diff --git a/CheckGit.cmake b/CheckGit.cmake
new file mode 100644
index 0000000..a558c57
--- /dev/null
+++ b/CheckGit.cmake
@@ -0,0 +1,61 @@
+set(pre_configure_file ${CMAKE_CURRENT_SOURCE_DIR}/src/MeshField_GetHash.cpp.in)
+set(post_configure_file ${CMAKE_CURRENT_BINARY_DIR}/MeshField_GetHash.cpp)
+
+function(CheckGitWrite git_hash)
+ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/git-state.txt "${git_hash}\n")
+endfunction()
+
+function(CheckGitRead git_hash)
+ if (EXISTS ${CMAKE_CURRENT_BINARY_DIR}/git-state.txt)
+ file(STRINGS ${CMAKE_CURRENT_BINARY_DIR}/git-state.txt CONTENT)
+ LIST(GET CONTENT 0 var)
+
+ set(${git_hash} ${var} PARENT_SCOPE)
+ endif ()
+endfunction()
+
+function(CheckGitVersion)
+ execute_process(COMMAND git rev-parse HEAD
+ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+ OUTPUT_VARIABLE GIT_HASH
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ CheckGitRead(GIT_HASH_CACHE)
+ if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR})
+ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+ endif ()
+
+ if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/MeshField_GetHash.hpp)
+ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/MeshField_GetHash.hpp DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+ endif()
+
+ if (NOT DEFINED GIT_HASH_CACHE)
+ set(GIT_HASH_CACHE "INVALID")
+ endif ()
+
+ if (NOT ${GIT_HASH} STREQUAL ${GIT_HASH_CACHE} OR NOT EXISTS ${post_configure_file})
+ CheckGitWrite(${GIT_HASH})
+
+ configure_file(${pre_configure_file} ${post_configure_file} @ONLY)
+ endif ()
+
+endfunction()
+
+function(CheckGitSetup)
+
+ add_custom_target(AlwaysCheckGit COMMAND ${CMAKE_COMMAND}
+ -DRUN_CHECK_GIT_VERSION=1
+ -Dpre_configure_dir=${CMAKE_CURRENT_SOURCE_DIR}
+ -Dpost_configure_file=${CMAKE_CURRENT_BINARY_DIR}
+ -DGIT_HASH_CACHE=${GIT_HASH_CACHE}
+ -P ${CMAKE_CURRENT_SOURCE_DIR}/CheckGit.cmake
+ BYPRODUCTS ${post_configure_file}
+ )
+
+
+ CheckGitVersion()
+endfunction()
+
+if (RUN_CHECK_GIT_VERSION)
+ CheckGitVersion()
+endif ()
diff --git a/docs/Ordering.tex b/docs/Ordering.tex
new file mode 100644
index 0000000..89e536d
--- /dev/null
+++ b/docs/Ordering.tex
@@ -0,0 +1,87 @@
+\documentclass{article}
+\usepackage[letterpaper,textwidth=5in,right=2.5in,textheight=9in]{geometry}
+\usepackage{tikz}
+\pagestyle{empty}
+\begin{document}
+\noindent
+Joshua Kloepfer\\
+9/30/2025\\
+\textbf{Meshfields DOF Holder and Integration Point Ordering}\\\\
+The node ordering and parametric coordinates below follow
+Zienkiewicz, Taylor, and Zhu, ``The Finite Element Method: Its Basis and
+Fundamentals'', 2013, and match the shape functions defined in
+\texttt{src/MeshField\_Shape.hpp}.\\
+\\
+Linear Triangle:\\
+\begin{tikzpicture}[scale=0.8]
+ \draw[gray, thick] (0, 0) -- (0, 5);
+ \draw[gray, thick] (0, 0) -- (5, 0);
+ \draw[gray, thick] (0, 5) -- (5, 0);
+ \draw[->] (-1, 0) -- (-1, 2) node[anchor=east]{$\xi_1$};
+ \draw[->] (0, -1) -- (2, -1) node[anchor=north]{$\xi_0$};
+ \filldraw[black] (0, 0) circle (2pt) node[anchor=north east]{V0 $(0,0)$};
+ \filldraw[black] (5, 0) circle (2pt) node[anchor=west]{V1 $(1,0)$};
+ \filldraw[black] (0, 5) circle (2pt) node[anchor=south]{V2 $(0,1)$};
+\end{tikzpicture}
+\\
+Quadratic Triangle\\
+\begin{tikzpicture}[scale=0.8]
+ \draw[gray, thick] (0, 0) -- (0, 5);
+ \draw[gray, thick] (0, 0) -- (5, 0);
+ \draw[gray, thick] (0, 5) -- (5, 0);
+ \draw[->] (-1, 0) -- (-1, 2) node[anchor=east]{$\xi_1$};
+ \draw[->] (0, -1) -- (2, -1) node[anchor=north]{$\xi_0$};
+ \filldraw[black] (0, 0) circle (2pt) node[anchor=north east]{V0 $(0,0)$};
+ \filldraw[black] (5, 0) circle (2pt) node[anchor=west]{V1 $(1,0)$};
+ \filldraw[black] (0, 5) circle (2pt) node[anchor=south]{V2 $(0,1)$};
+ \filldraw[black] (2.5, 0) circle (2pt) node[anchor=north]{E0};
+ \filldraw[black] (2.5, 2.5) circle (2pt) node[anchor=south west]{E1};
+ \filldraw[black] (0, 2.5) circle (2pt) node[anchor=east]{E2};
+\end{tikzpicture}
+\\
+Edge nodes: E0 on V0--V1, E1 on V1--V2, E2 on V0--V2;
+these are nodes 3, 4 and 5 of \texttt{QuadraticTriangleShape}.\\
+Linear Tetrahedron:\\
+\begin{tikzpicture}[scale=0.8]
+ \draw[gray, thick] (0, 0, 0) -- (5, 0, 0);
+ \draw[gray, thick] (0, 0, 0) -- (0, 5, 0);
+ \draw[gray, thick] (5, 0, 0) -- (0, 5, 0);
+ \draw[gray, thick] (0, 0, 0) -- (0, 0, 5);
+ \draw[gray, thick] (5, 0, 0) -- (0, 0, 5);
+ \draw[gray, thick] (0, 5, 0) -- (0, 0, 5);
+ \filldraw[black] (0, 0, 0) circle (2pt) node[anchor=north east]{V0 $(0,0,0)$};
+ \filldraw[black] (5, 0, 0) circle (2pt) node[anchor=west]{V1 $(1,0,0)$};
+ \filldraw[black] (0, 5, 0) circle (2pt) node[anchor=south]{V2 $(0,1,0)$};
+ \filldraw[black] (0, 0, 5) circle (2pt) node[anchor=east]{V3 $(0,0,1)$};
+\end{tikzpicture}
+\\
+Vertex node $i$ is at $L_i = 1$: V0 at the origin, V1 on $\xi_0$,
+V2 on $\xi_1$, V3 on $\xi_2$. Vertex ordering must follow the right hand
+rule, so that the Jacobian determinant is positive.\\
+Quadratic Tetrahedron:\\
+\begin{tikzpicture}[scale=0.8]
+ \draw[gray, thick] (0, 0, 0) -- (5, 0, 0);
+ \draw[gray, thick] (0, 0, 0) -- (0, 5, 0);
+ \draw[gray, thick] (5, 0, 0) -- (0, 5, 0);
+ \draw[gray, thick] (0, 0, 0) -- (0, 0, 5);
+ \draw[gray, thick] (5, 0, 0) -- (0, 0, 5);
+ \draw[gray, thick] (0, 5, 0) -- (0, 0, 5);
+ \filldraw[black] (0, 0, 0) circle (2pt) node[anchor=north east]{V0};
+ \filldraw[black] (5, 0, 0) circle (2pt) node[anchor=west]{V1};
+ \filldraw[black] (0, 5, 0) circle (2pt) node[anchor=south]{V2};
+ \filldraw[black] (0, 0, 5) circle (2pt) node[anchor=east]{V3};
+
+ \filldraw[black] (2.5, 0, 0) circle (2pt) node[anchor=north]{E0};
+ \filldraw[black] (0, 2.5, 0) circle (2pt) node[anchor=west]{E1};
+ \filldraw[black] (0, 0, 2.5) circle (2pt) node[anchor=east]{E2};
+ \filldraw[black] (2.5, 2.5, 0) circle (2pt) node[anchor=west]{E3};
+ \filldraw[black] (0, 2.5, 2.5) circle (2pt) node[anchor=west]{E4};
+ \filldraw[black] (2.5, 0, 2.5) circle (2pt) node[anchor=north]{E5};
+\end{tikzpicture}
+\\
+Edge nodes, corresponding to nodes 4--9 of
+\texttt{QuadraticTetrahedronShape}: E0 on V0--V1, E1 on V0--V2,
+E2 on V0--V3, E3 on V1--V2, E4 on V2--V3, E5 on V1--V3.
+Vertex ordering must follow the right hand rule and edge ordering must
+follow the diagram.
+\end{document}
diff --git a/docs/addingShapeFunctionsForOmegah.md b/docs/addingShapeFunctionsForOmegah.md
index ff3f2bc..638abea 100644
--- a/docs/addingShapeFunctionsForOmegah.md
+++ b/docs/addingShapeFunctionsForOmegah.md
@@ -52,6 +52,9 @@ omitted:
| Triangle (2D) | \f$(\xi_0,\xi_1)\f$ | \f$[0,1]^2\f$, \f$\xi_0+\xi_1 \le 1\f$ |
| Tet (3D) | \f$(\xi_0,\xi_1,\xi_2)\f$ | \f$[0,1]^3\f$, \f$\sum \xi_i \le 1\f$ |
+Diagrams of the vertex and edge node ordering, with parametric coordinates, for
+the linear and quadratic triangle and tetrahedron are in `docs/Ordering.tex`.
+
---
## Step 1 - Define the Shape Function Struct {#step1}
@@ -107,10 +110,8 @@ The mapping struct lives in `src/MeshField.hpp` inside
(as numbered by the shape function) into the on-process mesh entity indices
(as numbered by Omega\_h) that hold the corresponding DOFs.
-### Vertex-Ordering Correction
+### Mapping Omega\_h Vertices and Edges to Nodes
-As previously stated, Omega\_h numbers vertices and edges within an element
-differently from the MeshFields canonical ordering used by the shape functions.
The Omega\_h canonical orderings for simplices are depicted below:
@@ -124,27 +125,50 @@ Omega_h tetrahedron ordering.
Meshes composed of hypercubes (quadrilaterals and hexahedrons), pyramids, and prisms are supported by Omega_h, but they cannot be adapted.
Version 1.1.0 of MeshFields only supports Omega\_h simplices.
-The existing linear-triangle and linear-tetrahedron mappings correct for
-the difference in Omega\_h vs MeshFields ordering with a cyclic rotation.
+**Vertices.** The MeshFields vertex node ordering for triangles and tetrahedra
+matches the Omega\_h element-to-vertex ordering, so vertex node \f$i\f$ maps
+directly to local vertex \f$i\f$ with no rotation:
```cpp
-// For triangles (triDim=2, vtxDim=0):
+// For triangles (triDim=2) and tetrahedra (tetDim=3), vtxDim=0:
const auto localVtxIdx =
- (Omega_h::simplex_down_template(triDim, vtxDim, nodeIdx, /*ignored=*/-1) + 2) % 3;
+ Omega_h::simplex_down_template(elemDim, vtxDim, nodeIdx, /*ignored=*/-1);
+```
-// For tetrahedra (tetDim=3, vtxDim=0):
-const auto localVtxIdx =
- (Omega_h::simplex_down_template(tetDim, vtxDim, nodeIdx, /*ignored=*/-1) + 3) % 4;
+**Edges.** Edge nodes do not necessarily follow the Omega\_h element-to-edge
+ordering, so the mapping struct uses a lookup table from node index to the
+Omega\_h local edge index.
+For the quadratic triangle the MeshFields edges (E0: V0-V1, E1: V1-V2,
+E2: V0-V2) coincide with the Omega\_h edges, so the table is the identity.
+For the quadratic tetrahedron the MeshFields edges (nodes 4-9: V0-V1, V0-V2,
+V0-V3, V1-V2, V2-V3, V1-V3) are reordered to the Omega\_h edges
+(0: V0-V1, 1: V1-V2, 2: V2-V0, 3: V0-V3, 4: V1-V3, 5: V2-V3):
+
+```cpp
+// from QuadraticTetrahedronToField
+const MeshField::LO tetNode2DofHolder[10] = {0, 1, 2, 3, 0, 2, 3, 1, 5, 4};
```
-You must determine the correct rotation offset for your topology and DOF holders by
-comparing the Omega\_h canonical vertex/edge ordering (see
+You must determine the correct vertex and edge mapping for your topology and
+DOF holders by comparing the Omega\_h canonical vertex/edge ordering (see
figures above based on `Omega_h_simplex.hpp` and the `simplex_down_template` function)
with the node ordering established by `getNodeParametricCoords()` in your shape
struct. It is a good idea to add a unit test that evaluates the shape functions at
the parametric coordinates of each node and checks that the value for
that node is 1.0 and all others are 0.0.
+### Jacobian Orientation
+
+@ref MeshField::FieldElement "FieldElement" returns **signed** Jacobian
+determinants for 2D and 3D elements. The integrator
+(`src/MeshField_Integrate.hpp`) uses the determinants as-is, and debug builds
+assert that each one is non-negative.
+The vertex ordering produced by your mapping must therefore follow the
+right-hand rule so that the Jacobian determinant of a valid (un-inverted)
+Omega\_h element is positive.
+An incorrect vertex permutation results in a negative determinant and a
+failed assertion in debug builds.
+
### Interface Requirements
| Member | Description |
@@ -225,7 +249,7 @@ The steps within that branch are:
2. **Allocate storage** -- construct a `Controller` sized for each DOF-holding entity type. Each field in the controller needs entries for `numEntities * dofsPerHolder * numComp`.
3. **Create fields** -- call `MeshField::makeField(ctrl)` for each field index `N` to obtain typed slice objects.
4. **Construct the Accessor** -- pass the slice objects to your Accessor's constructor (e.g., `QuadraticAccessor{vtxField, edgeField}`).
-5. **Return** `FieldWithController>{ctrl, ShapeField(meshInfo, accessor)}`.
+5. **Return** `FieldWithController>{ctrl, ShapeField(meshInfo, accessor)}`.
See the existing `order == 2 && dim == 2` branch (quadratic triangle) as a
concrete reference:
diff --git a/pumiTest/CMakeLists.txt b/pumiTest/CMakeLists.txt
new file mode 100644
index 0000000..695a477
--- /dev/null
+++ b/pumiTest/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.16)
+project(PumiTest LANGUAGES CXX C)
+
+find_package(MPI REQUIRED)
+
+find_package(SCOREC REQUIRED)
+
+add_executable(PumiTest main.cpp)
+
+target_link_libraries(PumiTest PRIVATE
+ MPI::MPI_CXX
+ SCOREC::apf
+ SCOREC::mds
+ SCOREC::gmi
+ SCOREC::pcu
+ SCOREC::lion
+)
diff --git a/pumiTest/main.cpp b/pumiTest/main.cpp
new file mode 100644
index 0000000..9ae4bf2
--- /dev/null
+++ b/pumiTest/main.cpp
@@ -0,0 +1,56 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class CountIntegrator : public apf::Integrator {
+ protected:
+ double numEnt;
+ apf::MeshElement* currentMeshElement;
+ public:
+ double getCount() {return numEnt;}
+ void resetCount() { numEnt = 0; }
+ CountIntegrator(size_t order) : Integrator(order), numEnt(0) {};
+ void inElement(apf::MeshElement* me) override {
+ currentMeshElement = me;
+ }
+ void atPoint(apf::Vector3 const& point, double weight, double jacobian) override {
+ apf::Vector3 globalCoord;
+ apf::mapLocalToGlobal(currentMeshElement, point, globalCoord);
+ numEnt += weight * globalCoord[0] * jacobian;
+ }
+};
+int main(int argc, char ** argv) {
+ pcu::Init(&argc, &argv);
+ {
+ pcu::PCU PCUObj;
+ size_t order = atoi(argv[1]);
+ size_t size = atoi(argv[2]);
+ size_t numberOfElems = atoi(argv[3]);
+ size_t dim = atoi(argv[4]);
+ size_t runs = atoi(argv[5]);
+ gmi_register_mesh();
+ gmi_register_null();
+ apf::Mesh2* mesh = apf::makeMdsBox(numberOfElems, numberOfElems, dim == 3 ? numberOfElems : 0, size, size, dim == 3 ? size : 0, 1, &PCUObj);
+ CountIntegrator * countInt = new CountIntegrator(order);
+ for (int i = 0; i < runs; ++i) {
+ countInt->resetCount();
+ auto start = std::chrono::high_resolution_clock::now();
+ countInt->process(mesh);
+ auto end = std::chrono::high_resolution_clock::now();
+ std::cout << "RESULT: " << pumi_git_sha() << "," << mesh->count(mesh->getDimension()) << "," << order << "," << countInt->getCount() << "," << std::chrono::duration(end - start).count() << std::endl;
+ }
+ delete countInt;
+ mesh->destroyNative();
+ apf::destroyMesh(mesh);
+ }
+ pcu::Finalize();
+ return 0;
+}
diff --git a/src/KokkosController.hpp b/src/KokkosController.hpp
index 0d2deec..29d6ca3 100644
--- a/src/KokkosController.hpp
+++ b/src/KokkosController.hpp
@@ -62,7 +62,7 @@ template struct KokkosSliceWrapper {
}
};
-template
+template
class KokkosController {
// type definitions
@@ -182,7 +182,8 @@ class KokkosController {
std::tuple...> values_;
public:
- typedef ExecutionSpace exe;
+ typedef ExeSpace exe;
+ using ExecutionSpace=exe;
static const int MAX_RANK = 5;
KokkosController() {
diff --git a/src/MeshField.hpp b/src/MeshField.hpp
index e9a38fc..a967837 100644
--- a/src/MeshField.hpp
+++ b/src/MeshField.hpp
@@ -53,6 +53,7 @@ struct SetCoordFieldFunctor
const CoordinateMap map_;
};
+
template
struct InterpolateElementFunctor
{
@@ -97,9 +98,7 @@ struct LinearTriangleToVertexField {
const auto vtxDim = 0;
const auto ignored = -1;
const auto localVtxIdx =
- (Omega_h::simplex_down_template(triDim, vtxDim, triNodeIdx, ignored) +
- 2) %
- 3;
+ Omega_h::simplex_down_template(triDim, vtxDim, triNodeIdx, ignored);
const auto triToVtxDegree = Omega_h::simplex_degree(triDim, vtxDim);
const MeshField::LO vtx = triVerts[(tri * triToVtxDegree) + localVtxIdx];
return {0, triCompIdx, vtx, MeshField::Vertex}; // node, comp, ent, topo
@@ -127,10 +126,7 @@ struct LinearTetrahedronToVertexField {
const auto tetDim = 3;
const auto vtxDim = 0;
const auto ignored = -1;
- const auto localVtxIdx =
- (Omega_h::simplex_down_template(tetDim, vtxDim, tetNodeIdx, ignored) +
- 3) %
- 4;
+ const auto localVtxIdx = Omega_h::simplex_down_template(tetDim, vtxDim, tetNodeIdx, ignored);
const auto tetToVtxDegree = Omega_h::simplex_degree(tetDim, vtxDim);
const MeshField::LO vtx = tetVerts[(tet * tetToVtxDegree) + localVtxIdx];
return {0, tetCompIdx, vtx, MeshField::Vertex}; // node, comp, ent, topo
@@ -178,10 +174,8 @@ struct QuadraticTriangleToField {
const auto triDim = 2;
const auto vtxDim = 0;
const auto ignored = -1;
- const auto localVtxIdx = (Omega_h::simplex_down_template(
- triDim, vtxDim, dofHolderIdx, ignored) +
- 2) %
- 3;
+ const auto localVtxIdx = Omega_h::simplex_down_template(
+ triDim, vtxDim, dofHolderIdx, ignored);
const auto triToVtxDegree = Omega_h::simplex_degree(triDim, vtxDim);
osh_ent = triVerts[(tri * triToVtxDegree) + localVtxIdx];
} else if (dofHolderTopo == MeshField::Edge) {
@@ -191,7 +185,7 @@ struct QuadraticTriangleToField {
// passing dofHolderIdx as Omega_h_simplex.hpp does not provide
// a function that maps a triangle and edge index to a 'canonical' edge
// index. This may need to be revisited...
- osh_ent = triEdges[(tri * triToEdgeDegree) + (dofHolderIdx + 2) % 3];
+ osh_ent = triEdges[(tri * triToEdgeDegree) + dofHolderIdx];
} else {
assert(false);
}
@@ -222,7 +216,7 @@ struct QuadraticTetrahedronToField {
operator()(MeshField::LO tetNodeIdx, MeshField::LO tetCompIdx,
MeshField::LO tet, MeshField::Mesh_Topology topo) const {
assert(topo == MeshField::Tetrahedron);
- const MeshField::LO tetNode2DofHolder[10] = {0, 1, 2, 3, 3, 4, 5, 0, 1, 2};
+ const MeshField::LO tetNode2DofHolder[10] = {0, 1, 2, 3, 0, 2, 3, 1, 5, 4};
const MeshField::Mesh_Topology tetNode2DofHolderTopo[10] = {
MeshField::Vertex, MeshField::Vertex, MeshField::Vertex,
MeshField::Vertex, MeshField::Edge, MeshField::Edge,
@@ -235,12 +229,8 @@ struct QuadraticTetrahedronToField {
const auto tetDim = 3;
const auto vtxDim = 0;
const auto ignored = -1;
- // cyclic rotation of the omegah vertex order to map to the meshfields order
- // defined by the shape functions in MeshField_Shape.hpp
- const auto localVtxIdx = (Omega_h::simplex_down_template(
- tetDim, vtxDim, dofHolderIdx, ignored) +
- 3) %
- 4;
+ const auto localVtxIdx = Omega_h::simplex_down_template(
+ tetDim, vtxDim, dofHolderIdx, ignored);
const auto tetToVtxDegree = Omega_h::simplex_degree(tetDim, vtxDim);
osh_ent = tetVerts[(tet * tetToVtxDegree) + localVtxIdx];
} else if (dofHolderTopo == MeshField::Edge) {
@@ -357,8 +347,8 @@ class OmegahMeshField {
if constexpr (order == 1) {
const auto meshDim = meshInfo.dim;
const auto coords = mesh.coords();
- MeshField::parallel_for(
- ExecutionSpace(), {0}, {meshInfo.numVtx},
+ MeshField::parallel_for(
+ {0}, {meshInfo.numVtx},
SetCoordFieldFunctor(field, coords, meshDim, map),
"setCoordField");
diff --git a/src/MeshField_Element.hpp b/src/MeshField_Element.hpp
index 1ab15d7..7e8fc55 100644
--- a/src/MeshField_Element.hpp
+++ b/src/MeshField_Element.hpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include
#include // getLastValue
#include
@@ -48,9 +49,9 @@ template class has_static_rank {
template class has_extent_method {
private:
template
- static auto test(int)
- -> decltype(std::declval().extent(std::declval()),
- std::true_type());
+ static auto
+ test(int) -> decltype(std::declval().extent(std::declval()),
+ std::true_type());
template static std::false_type test(...);
public:
@@ -308,8 +309,7 @@ struct FieldElement {
// TODO use nested parallel for?
auto Ji = Kokkos::subview(J, i, Kokkos::ALL(), Kokkos::ALL());
const auto cross = Ji(0, 0) * Ji(1, 1) - Ji(1, 0) * Ji(0, 1);
- const auto magnitude = Kokkos::fabs(cross);
- determinants(i) = magnitude;
+ determinants(i) = cross;
});
return determinants;
}
@@ -326,6 +326,80 @@ struct FieldElement {
return Kokkos::View("foo", J.extent(0));
}
+ Kokkos::View getJacobiansStaticLength(Kokkos::View localCoords) {
+ if (Debug) {
+ LO numErrors = 0;
+ Kokkos::parallel_reduce(
+ "checkCoords", localCoords.extent(0),
+ KOKKOS_LAMBDA(const int &ent, LO &lerrors) {
+ Real sum = 0;
+ LO isError = 0;
+ for (int i = 0; i < localCoords.extent(1); i++) {
+ if (localCoords(ent, i) < 0)
+ isError++;
+ sum += localCoords(ent, i);
+ }
+ if (Kokkos::fabs(sum - 1) > MachinePrecision)
+ isError++;
+ lerrors += isError;
+ },
+ numErrors);
+ if (numErrors) {
+ fail("One or more of the parametric coordinates passed "
+ "to evaluate(...) were invalid\n");
+ }
+ }
+ if (MeshEntDim != 1 && MeshEntDim != 2 && MeshEntDim != 3) {
+ fail("getJacobians only currently supports 1d, 2d, and 3d meshes. Input "
+ "mesh "
+ "has %zu dimensions.\n",
+ numMeshEnts);
+ }
+
+ if constexpr (MeshEntDim == 1) {
+ const auto numPts = localCoords.extent(0);
+ Kokkos::View res("result", numPts * numMeshEnts, 1, 1);
+ auto jacobianFunc = KOKKOS_CLASS_LAMBDA(const int ent, const int pt) {
+ const auto val = getJacobian1d(ent);
+ res(pt, 0, 0) = val;
+ };
+ MeshField::parallel_for({0, 0},
+ {numMeshEnts, numPts}, jacobianFunc, "1dJacobian");
+ return res;
+ } else if constexpr (MeshEntDim == 2 || MeshEntDim == 3) {
+ const auto numPts = localCoords.extent(0);
+ Kokkos::View res("result", numPts * numMeshEnts, MeshEntDim,
+ MeshEntDim);
+ Kokkos::deep_copy(res, 0.0);
+ auto jacobianFunc = KOKKOS_CLASS_LAMBDA(const int ent, const int pt) {
+ const auto vals = getNodeValues(ent);
+ Kokkos::Array xi;
+ for (size_t d = 0; d < MeshEntDim; d++)
+ xi[d] = localCoords(pt, d);
+ const auto grad = shapeFn.getLocalGradients(xi);
+ auto A = Kokkos::subview(res, ent * numPts + pt, Kokkos::ALL(),
+ Kokkos::ALL());
+ Real localA[MeshEntDim][MeshEntDim] = {};
+ for (size_t node = 0; node < ShapeType::numNodes; node++) {
+ for (size_t i = 0; i < MeshEntDim; ++i) {
+ for (size_t j = 0; j < MeshEntDim; ++j) {
+ localA[j][i] += vals[node * MeshEntDim + i] *
+ grad[node * MeshEntDim + j];
+ }
+ }
+ }
+ for (size_t i = 0; i < MeshEntDim; ++i) {
+ for (size_t j = 0; j < MeshEntDim; ++j) {
+ A(i, j) = localA[i][j];
+ }
+ }
+ };
+ MeshField::parallel_for({0, 0},
+ {numMeshEnts, numPts}, jacobianFunc, "2d3dJacobian");
+ return res;
+ }
+ }
+
/**
* @brief
* Given an array of parametric coordinates 'localCoords', one per mesh
@@ -404,48 +478,80 @@ struct FieldElement {
// one matrix per point
Kokkos::View res("result", numPts, MeshEntDim, MeshEntDim);
Kokkos::deep_copy(res, 0.0); // initialize all entries to zero
-
- // fill the views of node coordinates and node gradients
- Kokkos::View nodeCoords(
- "nodeCoords", numPts);
- Kokkos::View nodalGradients(
- "nodalGradients", numPts);
Kokkos::parallel_for(
numMeshEnts, KOKKOS_CLASS_LAMBDA(const int ent) {
const auto vals = getNodeValues(ent);
assert(vals.size() == MeshEntDim * ShapeType::numNodes);
for (auto pt = offsets(ent); pt < offsets(ent + 1); pt++) {
+ auto A = Kokkos::subview(res, pt, Kokkos::ALL(), Kokkos::ALL());
+ Real localA[MeshEntDim][MeshEntDim] = {};
Kokkos::Array xi;
for (size_t d = 0; d < MeshEntDim; d++)
xi[d] = localCoords(pt, d);
const auto grad = shapeFn.getLocalGradients(xi);
for (size_t node = 0; node < ShapeType::numNodes; node++) {
- for (size_t d = 0; d < MeshEntDim; d++) {
- nodeCoords(pt, node, d) = vals[node * MeshEntDim + d];
- nodalGradients(pt, node, d) = grad[node * MeshEntDim + d];
+ for (size_t i = 0; i < MeshEntDim; ++i) {
+ for (size_t j = 0; j < MeshEntDim; ++j) {
+ localA[j][i] += vals[node * MeshEntDim + i] *
+ grad[node * MeshEntDim + j];
+ }
}
}
- }
- });
-
- Kokkos::parallel_for(
- numMeshEnts, KOKKOS_LAMBDA(const int ent) {
- // TODO use nested parallel for?
- for (auto pt = offsets(ent); pt < offsets(ent + 1); pt++) {
- auto A = Kokkos::subview(res, pt, Kokkos::ALL(), Kokkos::ALL());
- for (size_t node = 0; node < ShapeType::numNodes; node++) {
- auto a =
- Kokkos::subview(nodalGradients, pt, node, Kokkos::ALL());
- auto b = Kokkos::subview(nodeCoords, pt, node, Kokkos::ALL());
- addTensorProduct(a, b, A);
+ for (size_t i = 0; i < MeshEntDim; ++i) {
+ for (size_t j = 0; j < MeshEntDim; ++j) {
+ A(i, j) = localA[i][j];
+ }
}
}
});
+
return res;
}
}
};
+template
+Kokkos::View
+evaluateStaticLength(FieldElement &fes, Kokkos::View localCoords) {
+ if (Debug) {
+ LO numErrors = 0;
+ Kokkos::parallel_reduce(
+ "checkCoords", localCoords.extent(0),
+ KOKKOS_LAMBDA(const int &ent, LO &lerrors) {
+ Real sum = 0;
+ LO isError = 0;
+ for (int i = 0; i < localCoords.extent(1); i++) {
+ if (localCoords(ent, i) < 0)
+ isError++;
+ sum += localCoords(ent, i);
+ }
+ if (Kokkos::fabs(sum - 1) > MachinePrecision)
+ isError++;
+ lerrors += isError;
+ },
+ numErrors);
+ if (numErrors) {
+ fail("One or more of the parametric coordinates passed "
+ "to evaluate(...) were invalid\n");
+ }
+ }
+ constexpr const auto numComponents = FieldElement::ValArray::size();
+ const auto numPts = localCoords.extent(0);
+ Kokkos::View res("result", numPts * fes.numMeshEnts);
+ auto evaluateFunc = KOKKOS_LAMBDA(const int ent, const int pt) {
+ Kokkos::Array lc;
+ for (int i = 0; i < localCoords.extent(1); ++i)
+ lc[i] = localCoords(pt, i);
+ const auto val = fes.getValue(ent, lc);
+ for (int i = 0; i < numComponents; ++i) {
+ res(ent * numPts + pt, i) = val[i];
+ }
+ };
+ MeshField::parallel_for({0, 0}, {fes.numMeshEnts, numPts}, evaluateFunc,
+ "evaluate");
+ return res;
+}
+
/**
* @brief
* Given an array of parametric coordinates 'localCoords', one per mesh element,
@@ -465,9 +571,9 @@ struct FieldElement {
* @return Kokkos::View of evaluation results for all the mesh elements
*/
template
-Kokkos::View
-evaluate(FieldElement &fes, Kokkos::View localCoords,
- Kokkos::View offsets) {
+Kokkos::View evaluate(
+ FieldElement &fes, Kokkos::View localCoords,
+ Kokkos::View offsets) {
if (Debug) {
// check input parametric coords are positive and sum to one
LO numErrors = 0;
diff --git a/src/MeshField_For.hpp b/src/MeshField_For.hpp
index 3b605ad..caeabd8 100644
--- a/src/MeshField_For.hpp
+++ b/src/MeshField_For.hpp
@@ -3,25 +3,44 @@
#include "MeshField_Utility.hpp"
#include
+#include "KokkosController.hpp"
+#ifdef MESHFIELDS_ENABLE_CABANA
+#include "MeshField_SimdFor.hpp"
+#endif
namespace MeshField {
-template
-void parallel_for(ExecutionSpace, const std::initializer_list &start,
+template class Y> struct checkController {
+ static constexpr bool value = false;
+};
+
+template class T, typename... innerArgs>
+struct checkController, T> {
+ static constexpr bool value = true;
+};
+template
+void parallel_for(const std::initializer_list &start,
const std::initializer_list &end,
const FunctorType &vectorKernel, std::string tag) {
- constexpr auto funcRank = MeshFieldUtil::function_traits::arity;
- assert(funcRank >= 1);
- Kokkos::Array a_start =
- MeshFieldUtil::to_kokkos_array(start);
- Kokkos::Array a_end =
- MeshFieldUtil::to_kokkos_array(end);
- if constexpr (funcRank == 1) {
- Kokkos::RangePolicy p(a_start[0], a_end[0]);
- Kokkos::parallel_for(tag, p, vectorKernel);
- } else {
- Kokkos::MDRangePolicy, ExecutionSpace> policy(
- a_start, a_end);
- Kokkos::parallel_for(tag, policy, vectorKernel);
+ using ExecutionSpace = Controller::ExecutionSpace;
+ if constexpr (checkController::value || forceKokkos) {
+ constexpr auto funcRank = MeshFieldUtil::function_traits::arity;
+ assert(funcRank >= 1);
+ Kokkos::Array a_start =
+ MeshFieldUtil::to_kokkos_array(start);
+ Kokkos::Array a_end =
+ MeshFieldUtil::to_kokkos_array(end);
+ if constexpr (funcRank == 1) {
+ Kokkos::RangePolicy p(a_start[0], a_end[0]);
+ Kokkos::parallel_for(tag, p, vectorKernel);
+ } else {
+ Kokkos::MDRangePolicy, ExecutionSpace> policy(
+ a_start, a_end);
+ Kokkos::parallel_for(tag, policy, vectorKernel);
+ }
+ }
+ else {
+ simd_parallel_for(start, end, vectorKernel, tag);
}
+
}
} // namespace MeshField
diff --git a/src/MeshField_GetHash.cpp.in b/src/MeshField_GetHash.cpp.in
new file mode 100644
index 0000000..287434a
--- /dev/null
+++ b/src/MeshField_GetHash.cpp.in
@@ -0,0 +1,2 @@
+#include "MeshField_GetHash.hpp"
+const char * GitHash = "@GIT_HASH@";
diff --git a/src/MeshField_GetHash.hpp b/src/MeshField_GetHash.hpp
new file mode 100644
index 0000000..999ce02
--- /dev/null
+++ b/src/MeshField_GetHash.hpp
@@ -0,0 +1,6 @@
+#ifndef GETHASH
+#define GETHASH
+
+extern const char *GitHash;
+
+#endif
diff --git a/src/MeshField_Integrate.hpp b/src/MeshField_Integrate.hpp
index da80ba4..6587644 100644
--- a/src/MeshField_Integrate.hpp
+++ b/src/MeshField_Integrate.hpp
@@ -142,23 +142,19 @@ Kokkos::View getIntegrationPointLocalCoords(
FieldElement &fes,
std::vector> ip) {
const auto numPtsPerElm = ip.size();
- const auto numMeshEnts = fes.numMeshEnts;
- const auto numParametricCoords = fes.MeshEntDim;
- Kokkos::View localCoords(
- "localCoords", numMeshEnts * numPtsPerElm, numParametricCoords);
- // broadcast the points into the view - FIXME this is an inefficient use of
- // memory
+ const auto meshEntDim = fes.MeshEntDim;
+ const auto numParametricCoords = meshEntDim;
+ Kokkos::View localCoords("localCoords", numPtsPerElm,
+ numParametricCoords);
+ auto hostLocalCoords = Kokkos::create_mirror_view(localCoords);
for (size_t pt = 0; pt < numPtsPerElm; pt++) {
const auto point = ip.at(pt);
const auto param = point.param;
- assert(numParametricCoords == param.size());
- Kokkos::parallel_for(
- numMeshEnts, KOKKOS_LAMBDA(const int ent) {
- for (size_t d = 0; d < numParametricCoords; d++) {
- localCoords(ent * numPtsPerElm + pt, d) = param[d];
- }
- });
+ for (size_t d = 0; d < numParametricCoords; ++d) {
+ hostLocalCoords(pt, d) = param[d];
+ }
}
+ Kokkos::deep_copy(localCoords, hostLocalCoords);
return localCoords;
}
@@ -167,25 +163,22 @@ Kokkos::View getIntegrationPointWeights(
FieldElement &fes,
std::vector> ip) {
const auto numPtsPerElm = ip.size();
- const auto numMeshEnts = fes.numMeshEnts;
- Kokkos::View weights("weights", numMeshEnts * numPtsPerElm);
- // broadcast the points into the view - FIXME this is an inefficient use of
- // memory
+ const auto meshEntDim = fes.MeshEntDim;
+ Kokkos::View weights("weights", numPtsPerElm);
+ auto hostWeights = Kokkos::create_mirror_view(weights);
for (size_t pt = 0; pt < numPtsPerElm; pt++) {
const auto point = ip.at(pt);
const auto w = point.weight;
- Kokkos::parallel_for(
- numMeshEnts,
- KOKKOS_LAMBDA(const int ent) { weights(ent * numPtsPerElm + pt) = w; });
+ hostWeights(pt) = w;
}
+ Kokkos::deep_copy(weights, hostWeights);
return weights;
}
template
auto getJacobianDeterminants(FieldElement &fes,
- Kokkos::View localCoords,
- size_t numIntegrationPoints) {
- auto J = getJacobians(fes, localCoords, numIntegrationPoints);
+ Kokkos::View localCoords) {
+ auto J = fes.getJacobiansStaticLength(localCoords);
auto dV = getJacobianDeterminants(fes, J);
return dV;
}
@@ -258,11 +251,13 @@ class Integrator {
auto ip = getIntegrationPoints(order);
auto localCoords = getIntegrationPointLocalCoords(fes, ip);
auto weights = getIntegrationPointWeights(fes, ip);
- auto dV = getJacobianDeterminants(fes, localCoords, ip.size());
+ auto dV = getJacobianDeterminants(fes, localCoords);
// 3d determinants are signed
+ if (Debug) {
Kokkos::parallel_for(
"absJacobianDeterminants", dV.extent(0),
- KOKKOS_LAMBDA(const int i) { dV(i) = Kokkos::fabs(dV(i)); });
+ KOKKOS_LAMBDA(const int i) { KOKKOS_ASSERT(dV(i) >= 0) });
+}
atPoints(localCoords, weights, dV);
parallelReduce();
post();
diff --git a/src/MeshField_SPR_ErrorEstimator.hpp b/src/MeshField_SPR_ErrorEstimator.hpp
index e45d1be..8cf7e29 100644
--- a/src/MeshField_SPR_ErrorEstimator.hpp
+++ b/src/MeshField_SPR_ErrorEstimator.hpp
@@ -91,10 +91,11 @@ class SelfProduct : public SInt {
void atPoints(Kokkos::View p,
Kokkos::View w,
Kokkos::View dV) {
-
- const size_t numPtsPerElem = p.extent(0) / estimation.mesh.nelems();
+ const size_t numPtsPerElem = p.extent(0);
+ const auto [shp, map] = Omegah::getTriangleElement::Order>(estimation.mesh);
+ FieldElement estimationField(estimation.mesh.nelems(), estimation.eps_star, shp, map);
auto eps_star_atPts =
- omf.triangleLocalPointEval(p, numPtsPerElem, estimation.eps_star);
+ evaluateStaticLength(estimationField, p);
r = 0;
Kokkos::parallel_reduce(
@@ -104,7 +105,7 @@ class SelfProduct : public SInt {
const auto last = first + numPtsPerElem;
for (auto pt = first; pt < last; pt++) {
const auto vPt = eps_star_atPts(pt, 0);
- const auto wPt = w(pt);
+ const auto wPt = w(pt % w.extent(0));
const auto dVPt = dV(pt);
r_local += (vPt * vPt) * wPt * dVPt;
}
@@ -137,7 +138,7 @@ error_par_reduce_impl(EstimationT &estimation, EpsStarT eps_star_atPts,
for (auto pt = first; pt < last; pt++) {
const auto eps_star_Pt = eps_star_atPts(pt, 0);
const auto diff = eps_elm - eps_star_Pt;
- const auto wPt = w(pt);
+ const auto wPt = w(pt % w.extent(0));
const auto dVPt = dV(pt);
sum += (diff * diff) * wPt * dVPt;
}
@@ -164,14 +165,16 @@ class Error : public SInt {
void atPoints(Kokkos::View p,
Kokkos::View w,
Kokkos::View dV) {
- const size_t numPtsPerElem = p.extent(0) / estimation.mesh.nelems();
+ const size_t numPtsPerElem = p.extent(0);
// FIXME eps isn't a ShapeField so we can't call
// omf.triangleLocalPointEval. For now, just get
// the value from the element and assert that there is
// one integration point per element.
assert(numPtsPerElem == 1);
+ const auto [shp, map] = Omegah::getTriangleElement::Order>(estimation.mesh);
+ FieldElement estimationField(estimation.mesh.nelems(), estimation.eps_star, shp, map);
auto eps_star_atPts =
- omf.triangleLocalPointEval(p, numPtsPerElem, estimation.eps_star);
+ evaluateStaticLength(estimationField, p);
r = error_par_reduce_impl(estimation, eps_star_atPts, errorNorm, w, dV,
numPtsPerElem);
}
diff --git a/src/MeshField_ShapeField.hpp b/src/MeshField_ShapeField.hpp
index 6178a80..708e80a 100644
--- a/src/MeshField_ShapeField.hpp
+++ b/src/MeshField_ShapeField.hpp
@@ -95,12 +95,13 @@ template struct FieldWithController {
* @param mixins object(s) needed to construct the Accessor
*/
//! [ShapeField]
-template
+template
struct ShapeField : public Mixins... {
Shape shape;
static const size_t numComp = numCompIn;
const MeshInfo meshInfo;
constexpr static auto Order = Shape::Order;
+ using Ctrlr=Controller;
// Mixins&&... binds only to rvalues/temporaries (braced-init-list call
// sites like ShapeField(meshInfo, {vtxField}) are fine; passing a named
// lvalue requires std::move). mixins is moved-from by std::forward below
@@ -247,8 +248,8 @@ auto CreateLagrangeField(const MeshInfo &meshInfo) {
// clang-format off
using LinearLagrangeShapeField = std::conditional_t<
dim == 3,
- ShapeField,
- ShapeField>;
+ ShapeField,
+ ShapeField>;
// clang-format on
LinearLagrangeShapeField llsf(meshInfo, {vtxField});
return FieldWithController{kk_ctrl, llsf};
@@ -293,8 +294,8 @@ auto CreateLagrangeField(const MeshInfo &meshInfo) {
// clang-format off
using QuadraticLagrangeShapeField = std::conditional_t<
dim == 3,
- ShapeField,
- ShapeField>;
+ ShapeField,
+ ShapeField>;
// clang-format on
QuadraticLagrangeShapeField qlsf(meshInfo, {vtxField, edgeField});
return FieldWithController{kk_ctrl,
diff --git a/src/MeshField_SimdFor.hpp b/src/MeshField_SimdFor.hpp
index 694a67f..df855fd 100644
--- a/src/MeshField_SimdFor.hpp
+++ b/src/MeshField_SimdFor.hpp
@@ -7,7 +7,7 @@
namespace {
template
typename std::enable_if<1 == MeshFieldUtil::function_traits::arity>::type
-simd_for(Fn &kernel, const Kokkos::Array &start,
+simd_for(const Fn &kernel, const Kokkos::Array &start,
const Kokkos::Array &end, std::string tag) {
Cabana::SimdPolicy policy(start[0], end[0]);
Cabana::simd_parallel_for(
@@ -21,7 +21,7 @@ simd_for(Fn &kernel, const Kokkos::Array &start,
template
typename std::enable_if<2 == MeshFieldUtil::function_traits::arity>::type
-simd_for(Fn &kernel, const Kokkos::Array &start,
+simd_for(const Fn &kernel, const Kokkos::Array &start,
const Kokkos::Array &end, std::string tag) {
Cabana::SimdPolicy policy(start[0], end[0]);
const int64_t s1 = start[1];
@@ -38,7 +38,7 @@ simd_for(Fn &kernel, const Kokkos::Array &start,
template
typename std::enable_if<3 == MeshFieldUtil::function_traits::arity>::type
-simd_for(Fn &kernel, const Kokkos::Array &start,
+simd_for(const Fn &kernel, const Kokkos::Array &start,
const Kokkos::Array &end, std::string tag) {
Cabana::SimdPolicy policy(start[0], end[0]);
const int s1 = start[1], s2 = start[2];
@@ -58,7 +58,7 @@ simd_for(Fn &kernel, const Kokkos::Array &start,
template
typename std::enable_if<4 == MeshFieldUtil::function_traits::arity>::type
-simd_for(Fn &kernel, const Kokkos::Array &start,
+simd_for(const Fn &kernel, const Kokkos::Array &start,
const Kokkos::Array &end, std::string tag) {
Cabana::SimdPolicy policy(start[0], end[0]);
const int s1 = start[1], s2 = start[2], s3 = start[3];
@@ -81,10 +81,9 @@ simd_for(Fn &kernel, const Kokkos::Array &start,
namespace MeshField {
template
-void simd_parallel_for(CabController,
- const std::initializer_list &start_init,
+void simd_parallel_for(const std::initializer_list &start_init,
const std::initializer_list &end_init,
- FunctorType &vectorKernel, std::string tag) {
+ const FunctorType &vectorKernel, std::string tag) {
static_assert(std::is_integral::value, "Integral required\n");
static_assert(std::is_integral::value, "Integral required\n");
constexpr std::size_t RANK =
diff --git a/test/testBoxIntegrator.cpp b/test/testBoxIntegrator.cpp
new file mode 100644
index 0000000..24393c0
--- /dev/null
+++ b/test/testBoxIntegrator.cpp
@@ -0,0 +1,121 @@
+#include "KokkosController.hpp"
+#include "MeshField.hpp"
+#include "MeshField_Element.hpp" //remove?
+#include "MeshField_Fail.hpp" //remove?
+#include "MeshField_For.hpp" //remove?
+#include "MeshField_ShapeField.hpp" //remove?
+#include "Omega_h_build.hpp"
+#include "Omega_h_file.hpp"
+#include "Omega_h_simplex.hpp"
+#include
+#include
+#include
+#include
+
+using ExecutionSpace = Kokkos::DefaultExecutionSpace;
+using MemorySpace = Kokkos::DefaultExecutionSpace::memory_space;
+
+template
+class testIntegrator : public MeshField::Integrator {
+private:
+ testIntegrator(){};
+
+protected:
+ MeshField::Real integral;
+ FieldElement &fes;
+
+public:
+ MeshField::Real getIntegral() { return integral; }
+ testIntegrator(FieldElement &fes_in, int order)
+ : Integrator(order), integral(0), fes(fes_in){};
+ void atPoints(Kokkos::View p,
+ Kokkos::View w,
+ Kokkos::View dV) {
+ const auto globalCoords = MeshField::evaluateStaticLength(fes, p);
+ Kokkos::parallel_reduce(
+ "integrate", globalCoords.extent(0),
+ KOKKOS_LAMBDA(const int &ent, MeshField::Real &integ) {
+ const auto x = globalCoords(ent, 0);
+ const auto y = globalCoords(ent, 1);
+ const auto z = globalCoords.extent(1) == 3 ? globalCoords(ent, 2) : 1;
+ integ += w(ent) * x * dV(ent);
+ },
+ integral);
+ }
+};
+
+template typename Controller, size_t ShapeOrder,
+ size_t dim>
+void doRun(Omega_h::Mesh &mesh,
+ MeshField::OmegahMeshField &omf) {
+ auto field =
+ omf.template CreateLagrangeField();
+ auto coords = mesh.coords();
+ Kokkos::parallel_for(
+ mesh.nverts(), KOKKOS_LAMBDA(int vtx) {
+ field.field(vtx, 0, 0, MeshField::Vertex) = coords[vtx * dim];
+ field.field(vtx, 0, 1, MeshField::Vertex) = coords[vtx * dim + 1];
+ if constexpr (dim == 3) {
+ field.field(vtx, 0, 2, MeshField::Vertex) = coords[vtx * dim + 2];
+ }
+ });
+ if (ShapeOrder == 2) {
+ auto edge2vtx = mesh.ask_down(1, 0).ab2b;
+ auto edgeMap = mesh.ask_down(dim, 1).ab2b;
+ Kokkos::parallel_for(
+ mesh.nedges(), KOKKOS_LAMBDA(int edge) {
+ const auto left = edge2vtx[edge * 2];
+ const auto right = edge2vtx[edge * 2 + 1];
+ const auto x = (coords[left * dim] + coords[right * dim]) / 2.0;
+ const auto y =
+ (coords[left * dim + 1] + coords[right * dim + 1]) / 2.0;
+ field.field(edge, 0, 0, MeshField::Edge) = x;
+ field.field(edge, 0, 1, MeshField::Edge) = y;
+ if constexpr (dim == 3) {
+ const auto z =
+ (coords[left * dim + 2] + coords[right * dim + 2]) / 2.0;
+ field.field(edge, 0, 2, MeshField::Edge) = z;
+ }
+ });
+ }
+
+ auto shapeSet = [&]() {
+ if constexpr (dim == 3) {
+ return MeshField::Omegah::getTetrahedronElement(mesh);
+ } else {
+ return MeshField::Omegah::getTriangleElement(mesh);
+ }
+ };
+ const auto [shp, map] = shapeSet();
+ MeshField::FieldElement fes(mesh.nelems(), field.field, shp, map);
+ testIntegrator testInt(fes, ShapeOrder);
+ testInt.process(fes);
+ std::cout << testInt.getIntegral() << std::endl;
+}
+
+int main(int argc, char **argv) {
+ Kokkos::initialize(argc, argv);
+ auto lib = Omega_h::Library(&argc, &argv);
+ auto world = lib.world();
+#ifdef MESHFIELDS_ENABLE_CABANA
+ {
+ const auto family = OMEGA_H_SIMPLEX;
+ const auto len = 1.0;
+ Omega_h::Mesh mesh3D =
+ Omega_h::build_box(world, family, 1.0, 1.0, 1.0, len, len, len);
+ MeshField::OmegahMeshField
+ omf3D(mesh3D);
+ Omega_h::Mesh mesh2D =
+ Omega_h::build_box(world, family, 1.0, 1.0, 0.0, len, len, 0.0);
+ MeshField::OmegahMeshField
+ omf2D(mesh2D);
+ doRun(mesh2D, omf2D);
+ doRun(mesh2D, omf2D);
+ doRun(mesh3D, omf3D);
+ doRun(mesh3D, omf3D);
+ }
+#endif
+ {}
+ Kokkos::finalize();
+ return 0;
+}
diff --git a/test/testCabana.cpp b/test/testCabana.cpp
index 815a1b4..55abe73 100644
--- a/test/testCabana.cpp
+++ b/test/testCabana.cpp
@@ -311,13 +311,13 @@ void testCabanaParallelFor() {
field0(i) = i;
assert(field0(i) == i);
};
- MeshField::simd_parallel_for(c1, {0}, {x}, vectorKernel, "simple_loop");
+ MeshField::simd_parallel_for({0}, {x}, vectorKernel, "simple_loop");
auto vectorKernel2 = KOKKOS_LAMBDA(const int &i, const int &j) {
field1(i, j) = i + j;
assert(field1(i, j) == i + j);
};
- MeshField::simd_parallel_for(c1, {0, 0}, {x, y}, vectorKernel2,
+ MeshField::simd_parallel_for({0, 0}, {x, y}, vectorKernel2,
"simple_loop");
auto vectorKernel3 =
@@ -325,7 +325,7 @@ void testCabanaParallelFor() {
field2(i, j, k) = i + j + k;
assert(field2(i, j, k) == i + j + k);
};
- MeshField::simd_parallel_for(c1, {0, 0, 0}, {x, y, z}, vectorKernel3,
+ MeshField::simd_parallel_for({0, 0, 0}, {x, y, z}, vectorKernel3,
"simple_loop");
auto vectorKernel4 =
@@ -333,7 +333,7 @@ void testCabanaParallelFor() {
field3(i, j, k, l) = i + j + k + l;
assert(field3(i, j, k, l) == i + j + k + l);
};
- MeshField::simd_parallel_for(c1, {0, 0, 0, 0}, {x, y, z, a}, vectorKernel4,
+ MeshField::simd_parallel_for({0, 0, 0, 0}, {x, y, z, a}, vectorKernel4,
"simple_loop");
}
{
@@ -350,13 +350,13 @@ void testCabanaParallelFor() {
field0(i) = i;
assert(field0(i) == i);
};
- MeshField::simd_parallel_for(c1, {0}, {x}, vectorKernel, "simple_loop");
+ MeshField::simd_parallel_for({0}, {x}, vectorKernel, "simple_loop");
auto vectorKernel2 = KOKKOS_LAMBDA(const int &i, const int &j) {
field1(i, j) = i + j;
assert(field1(i, j) == i + j);
};
- MeshField::simd_parallel_for(c1, {0, 0}, {x + 1, y}, vectorKernel2,
+ MeshField::simd_parallel_for({0, 0}, {x + 1, y}, vectorKernel2,
"simple_loop");
auto vectorKernel3 =
@@ -364,7 +364,7 @@ void testCabanaParallelFor() {
field2(i, j, k) = i + j + k;
assert(field2(i, j, k) == i + j + k);
};
- MeshField::simd_parallel_for(c1, {0, 0, 0}, {x + 2, y, z}, vectorKernel3,
+ MeshField::simd_parallel_for({0, 0, 0}, {x + 2, y, z}, vectorKernel3,
"simple_loop");
auto vectorKernel4 =
@@ -372,7 +372,7 @@ void testCabanaParallelFor() {
field3(i, j, k, l) = i + j + k + l;
assert(field3(i, j, k, l) == i + j + k + l);
};
- MeshField::simd_parallel_for(c1, {0, 0, 0, 0}, {x + 3, y, z, a},
+ MeshField::simd_parallel_for({0, 0, 0, 0}, {x + 3, y, z, a},
vectorKernel4, "simple_loop");
}
diff --git a/test/testControllerLifetime.cpp b/test/testControllerLifetime.cpp
index 9d76ca1..2cfc9b8 100644
--- a/test/testControllerLifetime.cpp
+++ b/test/testControllerLifetime.cpp
@@ -59,7 +59,7 @@ int main(int argc, char **argv) {
using LA = decltype(vtxField);
using EA = decltype(edgeField);
using QA = QuadraticAccessor;
- using QLSF = ShapeField;
+ using QLSF = ShapeField;
QLSF field(meshInfo, {vtxField, edgeField});
// f(x,y) = 2x + y ; set exact analytic values at vertices and edge
diff --git a/test/testControllerPerformance.cpp b/test/testControllerPerformance.cpp
index 224ab7b..700b870 100644
--- a/test/testControllerPerformance.cpp
+++ b/test/testControllerPerformance.cpp
@@ -36,7 +36,7 @@ int main(int argc, char **argv) {
};
for (int i = 0; i < runs; ++i) {
auto start = std::chrono::system_clock::now();
- MeshField::simd_parallel_for(cabCtrlr, {0, 0, 0}, {n, DIM1, DIM2},
+ MeshField::simd_parallel_for({0, 0, 0}, {n, DIM1, DIM2},
cabDim4, "CabTest");
auto duration = std::chrono::duration_cast(
std::chrono::system_clock::now() - start);
@@ -55,7 +55,7 @@ int main(int argc, char **argv) {
};
for (int i = 0; i < runs; ++i) {
auto start = std::chrono::system_clock::now();
- MeshField::parallel_for(ExecutionSpace(), {0, 0, 0}, {n, DIM1, DIM2},
+ MeshField::parallel_for({0, 0, 0}, {n, DIM1, DIM2},
kokkosDim4, "KokkosTest");
auto duration = std::chrono::duration_cast(
std::chrono::system_clock::now() - start);
diff --git a/test/testCountIntegrator.cpp b/test/testCountIntegrator.cpp
index 781c179..445d939 100644
--- a/test/testCountIntegrator.cpp
+++ b/test/testCountIntegrator.cpp
@@ -33,7 +33,7 @@ void setVertices(Omega_h::Mesh &mesh, AnalyticFunction func, ShapeField field) {
const auto y = coords[vtx * MeshDim + 1];
field(vtx, 0, 0, MeshField::Vertex) = func(x, y);
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {mesh.nverts()},
+ MeshField::parallel_for({0}, {mesh.nverts()},
setFieldAtVertices, "setFieldAtVertices");
}
diff --git a/test/testElementJacobian1d.cpp b/test/testElementJacobian1d.cpp
index e61aaa0..fd28dcc 100644
--- a/test/testElementJacobian1d.cpp
+++ b/test/testElementJacobian1d.cpp
@@ -38,7 +38,7 @@ void setEdgeCoords(size_t numVerts, Kokkos::View coords,
auto setFieldAtVertices = KOKKOS_LAMBDA(const int &vtx) {
field(vtx, 0, 0, MeshField::Vertex) = coords(vtx);
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {numVerts}, setFieldAtVertices,
+ MeshField::parallel_for({0}, {numVerts}, setFieldAtVertices,
"setFieldAtVertices");
}
diff --git a/test/testElementJacobian2d.cpp b/test/testElementJacobian2d.cpp
index 58663cd..032ed65 100644
--- a/test/testElementJacobian2d.cpp
+++ b/test/testElementJacobian2d.cpp
@@ -65,7 +65,7 @@ void setVtxCoords(size_t numVerts, size_t meshDim, TriangleTestCase testTri,
field(vtx, 0, 0, MeshField::Vertex) = coords(vtx * meshDim);
field(vtx, 0, 1, MeshField::Vertex) = coords(vtx * meshDim + 1);
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {numVerts}, setCoordField,
+ MeshField::parallel_for({0}, {numVerts}, setCoordField,
"setCoordField");
}
diff --git a/test/testElementJacobian3d.cpp b/test/testElementJacobian3d.cpp
index fccc6ec..21a18e5 100644
--- a/test/testElementJacobian3d.cpp
+++ b/test/testElementJacobian3d.cpp
@@ -56,7 +56,7 @@ void setVtxCoords(size_t numVerts, size_t meshDim, TetrahedronTestCase testTri,
field(vtx, 0, 1, MeshField::Vertex) = coords(vtx * meshDim + 1);
field(vtx, 0, 2, MeshField::Vertex) = coords(vtx * meshDim + 2);
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {numVerts}, setCoordField,
+ MeshField::parallel_for({0}, {numVerts}, setCoordField,
"setCoordField");
}
diff --git a/test/testIntegrator.cpp b/test/testIntegrator.cpp
new file mode 100644
index 0000000..6489578
--- /dev/null
+++ b/test/testIntegrator.cpp
@@ -0,0 +1,200 @@
+#include "KokkosController.hpp"
+#include "MeshField.hpp"
+#include "MeshField_Element.hpp" //remove?
+#include "MeshField_Fail.hpp" //remove?
+#include "MeshField_For.hpp" //remove?
+#include "MeshField_ShapeField.hpp" //remove?
+#include "Omega_h_build.hpp"
+#include "Omega_h_file.hpp"
+#include "Omega_h_simplex.hpp"
+#include
+#include
+#include
+#include
+
+using ExecutionSpace = Kokkos::DefaultExecutionSpace;
+using MemorySpace = Kokkos::DefaultExecutionSpace::memory_space;
+
+template
+class testIntegrator : public MeshField::Integrator {
+private:
+ testIntegrator(){};
+
+protected:
+ MeshField::Real integral;
+ FieldElement &fes;
+ int m;
+ int n;
+ int l;
+
+public:
+ MeshField::Real getIntegral() { return integral; }
+ testIntegrator(FieldElement &fes_in, int _m, int _n, int _l, int order)
+ : Integrator(order), integral(0), fes(fes_in), m(_m), n(_n), l(_l){};
+ void atPoints(Kokkos::View p,
+ Kokkos::View w,
+ Kokkos::View dV) {
+ const auto globalCoords = MeshField::evaluateStaticLength(fes, p);
+ const int _m = m;
+ const int _n = n;
+ const int _l = l;
+ Kokkos::parallel_reduce(
+ "integrate", p.extent(0),
+ KOKKOS_LAMBDA(const int &ent, MeshField::Real &integ) {
+ const auto x = globalCoords(ent, 0);
+ const auto y = globalCoords(ent, 1);
+ const auto z = globalCoords.extent(1) == 3 ? globalCoords(ent, 2) : 1;
+ integ += w(ent % w.extent(0)) * pow(x, _m) * pow(y, _n) * pow(z, _l) *
+ dV(ent);
+ },
+ integral);
+ }
+};
+
+template typename Controller, size_t ShapeOrder,
+ size_t dim>
+void doRun(Omega_h::Mesh &mesh,
+ MeshField::OmegahMeshField &omf) {
+ auto fieldWithCtrlr =
+ omf.template CreateLagrangeField();
+ auto field = fieldWithCtrlr.field;
+ auto coords = mesh.coords();
+ Kokkos::parallel_for(
+ mesh.nverts(), KOKKOS_LAMBDA(int vtx) {
+ field(vtx, 0, 0, MeshField::Vertex) = coords[vtx * dim];
+ field(vtx, 0, 1, MeshField::Vertex) = coords[vtx * dim + 1];
+ if constexpr (dim == 3) {
+ field(vtx, 0, 2, MeshField::Vertex) = coords[vtx * dim + 2];
+ }
+ });
+ if (ShapeOrder == 2) {
+ auto edge2vtx = mesh.ask_down(1, 0).ab2b;
+ auto edgeMap = mesh.ask_down(dim, 1).ab2b;
+ Kokkos::parallel_for(
+ mesh.nedges(), KOKKOS_LAMBDA(int edge) {
+ const auto left = edge2vtx[edge * 2];
+ const auto right = edge2vtx[edge * 2 + 1];
+ const auto x = (coords[left * dim] + coords[right * dim]) / 2.0;
+ const auto y =
+ (coords[left * dim + 1] + coords[right * dim + 1]) / 2.0;
+ field(edge, 0, 0, MeshField::Edge) = x;
+ field(edge, 0, 1, MeshField::Edge) = y;
+ if constexpr (dim == 3) {
+ const auto z =
+ (coords[left * dim + 2] + coords[right * dim + 2]) / 2.0;
+ field(edge, 0, 2, MeshField::Edge) = z;
+ }
+ });
+ }
+
+ auto shapeSet = [&]() {
+ if constexpr (dim == 3) {
+ return MeshField::Omegah::getTetrahedronElement(mesh);
+ } else {
+ return MeshField::Omegah::getTriangleElement(mesh);
+ }
+ };
+ const auto [shp, map] = shapeSet();
+ MeshField::FieldElement fes(1, field, shp, map);
+ const int maxn = 32;
+ int binom[maxn + 1][maxn + 1];
+ for (int n = 0; n <= maxn; n++) {
+ binom[n][0] = binom[n][n] = 1;
+ for (int k = 1; k < n; k++) {
+ binom[n][k] = binom[n - 1][k] + binom[n - 1][k - 1];
+ }
+ }
+ if (dim == 2) {
+ for (int p = 1; p <= ShapeOrder; ++p) {
+ for (int m = p; m >= 0; --m) {
+ int n = p - m;
+ testIntegrator testInt(fes, m, n, 0, p);
+ testInt.process(fes);
+ double exact = 1.0 / binom[p][m] / (p + 1) / (p + 2);
+ if (Kokkos::fabs(testInt.getIntegral() - exact) >
+ MeshField::MachinePrecision) {
+ std::stringstream ss;
+ ss << "Integration over "
+ << ((dim == 3) ? "Tetrahedron " : "Triangle ") << "with order "
+ << ShapeOrder << " failed\n";
+ MeshField::fail(ss.str());
+ }
+ }
+ }
+ } else {
+ for (int p = 1; p <= ShapeOrder; ++p) {
+ for (int l = p; l >= 0; --l) {
+ for (int m = p - l; m >= 0; --m) {
+ int n = p - l - m;
+ testIntegrator testInt(fes, l, m, n, p);
+ testInt.process(fes);
+ double exact = 1.0 / binom[p][l + m] / binom[l + m][l] / (p + 1) /
+ (p + 2) / (p + 3);
+ if (Kokkos::fabs(testInt.getIntegral() - exact) >
+ MeshField::MachinePrecision) {
+ std::stringstream ss;
+ ss << "Integration over "
+ << ((dim == 3) ? "Tetrahedron " : "Triangle ") << "with order "
+ << ShapeOrder << " failed\n";
+ MeshField::fail(ss.str());
+ }
+ }
+ }
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ Kokkos::initialize(argc, argv);
+ auto lib = Omega_h::Library(&argc, &argv);
+#ifdef MESHFIELDS_ENABLE_CABANA
+ {
+ Omega_h::Reals coords2D({0.0, 0.0, 1.0, 0.0, 0.0, 1.0});
+ Omega_h::Reals coords3D(
+ {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0});
+
+ Omega_h::LOs tris_to_verts({0, 1, 2});
+ Omega_h::LOs tets_to_verts({0, 1, 2, 3});
+ Omega_h::Mesh mesh2D(&lib);
+ Omega_h::Mesh mesh3D(&lib);
+ Omega_h::build_from_elems_and_coords(&mesh2D, OMEGA_H_SIMPLEX, 2,
+ tris_to_verts, coords2D);
+ Omega_h::build_from_elems_and_coords(&mesh3D, OMEGA_H_SIMPLEX, 3,
+ tets_to_verts, coords3D);
+
+ MeshField::OmegahMeshField
+ omf2D(mesh2D);
+ doRun(mesh2D, omf2D);
+ doRun(mesh2D, omf2D);
+ MeshField::OmegahMeshField
+ omf3D(mesh3D);
+ doRun(mesh3D, omf3D);
+ doRun(mesh3D, omf3D);
+ }
+#endif
+ {
+ Omega_h::Reals coords2D({0.0, 0.0, 1.0, 0.0, 0.0, 1.0});
+ Omega_h::Reals coords3D(
+ {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0});
+
+ Omega_h::LOs tris_to_verts({0, 1, 2});
+ Omega_h::LOs tets_to_verts({0, 1, 2, 3});
+ Omega_h::Mesh mesh2D(&lib);
+ Omega_h::Mesh mesh3D(&lib);
+ Omega_h::build_from_elems_and_coords(&mesh2D, OMEGA_H_SIMPLEX, 2,
+ tris_to_verts, coords2D);
+ Omega_h::build_from_elems_and_coords(&mesh3D, OMEGA_H_SIMPLEX, 3,
+ tets_to_verts, coords3D);
+
+ MeshField::OmegahMeshField
+ omf2D(mesh2D);
+ doRun(mesh2D, omf2D);
+ doRun(mesh2D, omf2D);
+ MeshField::OmegahMeshField
+ omf3D(mesh3D);
+ doRun(mesh3D, omf3D);
+ doRun(mesh3D, omf3D);
+ }
+ Kokkos::finalize();
+ return 0;
+}
diff --git a/test/testIntegratorMeasure.cpp b/test/testIntegratorMeasure.cpp
index 04a4f6d..8e90ce2 100644
--- a/test/testIntegratorMeasure.cpp
+++ b/test/testIntegratorMeasure.cpp
@@ -18,9 +18,9 @@ class MeasureIntegrator : public MeshField::Integrator {
Kokkos::View dV) override {
MeshField::Real sum = 0;
Kokkos::parallel_reduce(
- "integrateMeasure", w.extent(0),
+ "integrateMeasure", dV.extent(0),
KOKKOS_LAMBDA(const int i, MeshField::Real &lsum) {
- lsum += w(i) * dV(i);
+ lsum += w(i % w.extent(0)) * dV(i);
},
sum);
measure += sum;
diff --git a/test/testIntegratorPerformance.cpp b/test/testIntegratorPerformance.cpp
new file mode 100644
index 0000000..dd47693
--- /dev/null
+++ b/test/testIntegratorPerformance.cpp
@@ -0,0 +1,142 @@
+#include "KokkosController.hpp"
+#include "MeshField.hpp"
+#include "MeshField_Element.hpp" //remove?
+#include "MeshField_Fail.hpp" //remove?
+#include "MeshField_For.hpp" //remove?
+#include "MeshField_ShapeField.hpp" //remove?
+#include "Omega_h_build.hpp"
+#include "Omega_h_file.hpp"
+#include "Omega_h_simplex.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+
+using ExecutionSpace = Kokkos::DefaultExecutionSpace;
+using MemorySpace = Kokkos::DefaultExecutionSpace::memory_space;
+
+template
+class testIntegrator : public MeshField::Integrator {
+private:
+ testIntegrator(){};
+
+protected:
+ MeshField::Real integral;
+ FieldElement &fes;
+
+public:
+ MeshField::Real getIntegral() { return integral; }
+ testIntegrator(FieldElement &fes_in, int order)
+ : Integrator(order), integral(0), fes(fes_in){};
+ void atPoints(Kokkos::View p,
+ Kokkos::View w,
+ Kokkos::View dV) {
+ const auto globalCoords = MeshField::evaluateStaticLength(fes, p);
+ Kokkos::parallel_reduce(
+ "integrate", globalCoords.extent(0),
+ KOKKOS_LAMBDA(const int &ent, MeshField::Real &integ) {
+ const auto x = globalCoords(ent, 0);
+ const auto y = globalCoords(ent, 1);
+ const auto z = globalCoords.extent(1) == 3 ? globalCoords(ent, 2) : 1;
+ integ += w(ent % w.extent(0)) * x * dV(ent);
+ },
+ integral);
+ }
+};
+
+template typename Controller>
+void doRun(size_t size, size_t numberOfElems, Omega_h::Library &lib,
+ size_t runs) {
+ auto world = lib.world();
+ const auto family = OMEGA_H_SIMPLEX;
+ auto mesh = Omega_h::build_box(world, family, size, size, dim == 3 ? size : 0,
+ numberOfElems, numberOfElems,
+ dim == 3 ? numberOfElems : 0);
+ MeshField::OmegahMeshField omf(mesh);
+ auto fieldWithCtrlr =
+ omf.template CreateLagrangeField();
+ auto field = fieldWithCtrlr.field;
+ auto coords = mesh.coords();
+ Kokkos::parallel_for(
+ mesh.nverts(), KOKKOS_LAMBDA(int vtx) {
+ field(vtx, 0, 0, MeshField::Vertex) = coords[vtx * dim];
+ field(vtx, 0, 1, MeshField::Vertex) = coords[vtx * dim + 1];
+ if constexpr (dim == 3) {
+ field(vtx, 0, 2, MeshField::Vertex) = coords[vtx * dim + 2];
+ }
+ });
+ if (ShapeOrder == 2) {
+ auto edge2vtx = mesh.ask_down(1, 0).ab2b;
+ auto edgeMap = mesh.ask_down(dim, 1).ab2b;
+ Kokkos::parallel_for(
+ mesh.nedges(), KOKKOS_LAMBDA(int edge) {
+ const auto left = edge2vtx[edge * 2];
+ const auto right = edge2vtx[edge * 2 + 1];
+ const auto x = (coords[left * dim] + coords[right * dim]) / 2.0;
+ const auto y =
+ (coords[left * dim + 1] + coords[right * dim + 1]) / 2.0;
+ field(edge, 0, 0, MeshField::Edge) = x;
+ field(edge, 0, 1, MeshField::Edge) = y;
+ if constexpr (dim == 3) {
+ const auto z =
+ (coords[left * dim + 2] + coords[right * dim + 2]) / 2.0;
+ field(edge, 0, 2, MeshField::Edge) = z;
+ }
+ });
+ }
+ auto shapeSet = [&]() {
+ if constexpr (dim == 3) {
+ return MeshField::Omegah::getTetrahedronElement(mesh);
+ } else {
+ return MeshField::Omegah::getTriangleElement(mesh);
+ }
+ };
+ const auto [shp, map] = shapeSet();
+ MeshField::FieldElement fes(mesh.nelems(), field, shp, map);
+ for (int i = 0; i < runs; ++i) {
+ testIntegrator testInt(fes, ShapeOrder);
+ auto start = std::chrono::high_resolution_clock::now();
+ testInt.process(fes);
+ Kokkos::fence();
+ auto end = std::chrono::high_resolution_clock::now();
+ std::cout << "RESULT: " << GitHash << "," << mesh.nelems() << ","
+ << ShapeOrder << "," << testInt.getIntegral() << ","
+ << std::chrono::duration(end - start).count()
+ << std::endl;
+ }
+}
+
+int main(int argc, char **argv) {
+ Kokkos::initialize(argc, argv);
+ auto lib = Omega_h::Library(&argc, &argv);
+ bool useCab = true;
+ size_t order = 2;
+ size_t size = 2;
+ size_t dim = 3;
+ size_t numberOfElems = 1;
+ size_t runs = 1;
+ if (argc == 7) {
+ useCab = atoi(argv[1]);
+ order = atoi(argv[2]);
+ size = atoi(argv[3]);
+ numberOfElems = atoi(argv[4]);
+ dim = atoi(argv[5]);
+ runs = atoi(argv[6]);
+ }
+ // [dim][order][controller]
+ void (*funcCall[2][2][2])(size_t, size_t, Omega_h::Library &, size_t) = {
+ {{doRun<2, 1, MeshField::KokkosController>,
+ doRun<2, 1, MeshField::CabanaController>},
+ {doRun<2, 2, MeshField::KokkosController>,
+ doRun<2, 2, MeshField::CabanaController>}},
+ {{doRun<3, 1, MeshField::KokkosController>,
+ doRun<3, 1, MeshField::CabanaController>},
+ {doRun<3, 2, MeshField::KokkosController>,
+ doRun<3, 2, MeshField::CabanaController>}}};
+ funcCall[dim - 2][order - 1][useCab](size, numberOfElems, lib, runs);
+ Kokkos::finalize();
+ return 0;
+}
diff --git a/test/testKokkos.cpp b/test/testKokkos.cpp
index e1b1792..53c15e2 100644
--- a/test/testKokkos.cpp
+++ b/test/testKokkos.cpp
@@ -127,15 +127,15 @@ void testKokkosParallelFor() {
rk5(i, j, k, l, m) = i + j + k + l + m;
assert(rk5(i, j, k, l, m) == i + j + k + l + m);
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {a}, k1,
+ MeshField::parallel_for({0}, {a}, k1,
"testKokkosParallelFor(rank1)");
- MeshField::parallel_for(ExecutionSpace(), {0, 0}, {a, b}, k2,
+ MeshField::parallel_for({0, 0}, {a, b}, k2,
"testKokkosParallelFor(rank2)");
- MeshField::parallel_for(ExecutionSpace(), {0, 0, 0}, {a, b, c}, k3,
+ MeshField::parallel_for({0, 0, 0}, {a, b, c}, k3,
"testKokkosParallelFor(rank3)");
- MeshField::parallel_for(ExecutionSpace(), {0, 0, 0, 0}, {a, b, c, d}, k4,
+ MeshField::parallel_for({0, 0, 0, 0}, {a, b, c, d}, k4,
"testKokkosParallelFor(rank4)");
- MeshField::parallel_for(ExecutionSpace(), {0, 0, 0, 0, 0}, {a, b, c, d, e},
+ MeshField::parallel_for({0, 0, 0, 0, 0}, {a, b, c, d, e},
k5, "testKokkosParallelFor(rank5)");
}
diff --git a/test/testOmegahTet.cpp b/test/testOmegahTet.cpp
index 2514e2b..edb5cdb 100644
--- a/test/testOmegahTet.cpp
+++ b/test/testOmegahTet.cpp
@@ -85,7 +85,7 @@ void setVertices(Omega_h::Mesh &mesh, AnalyticFunction func, ShapeField field) {
field(vtx, 0, i, MeshField::Vertex) = func(Vec<3>{x, y, z});
}
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {mesh.nverts()},
+ MeshField::parallel_for({0}, {mesh.nverts()},
setFieldAtVertices, "setFieldAtVertices");
}
@@ -110,7 +110,7 @@ void setEdges(Omega_h::Mesh &mesh, AnalyticFunction func, ShapeField field) {
field(edge, 0, i, MeshField::Edge) = func(Vec<3>{x, y, z});
}
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {mesh.nedges()},
+ MeshField::parallel_for({0}, {mesh.nedges()},
setFieldAtEdges, "setFieldAtEdges");
}
diff --git a/test/testOmegahTri.cpp b/test/testOmegahTri.cpp
index 8d40d97..fcc28be 100644
--- a/test/testOmegahTri.cpp
+++ b/test/testOmegahTri.cpp
@@ -83,7 +83,7 @@ void setVertices(Omega_h::Mesh &mesh, AnalyticFunction func, ShapeField field) {
field(vtx, 0, i, MeshField::Vertex) = func(Vec<2>{x, y});
}
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {mesh.nverts()},
+ MeshField::parallel_for({0}, {mesh.nverts()},
setFieldAtVertices, "setFieldAtVertices");
}
@@ -106,7 +106,7 @@ void setEdges(Omega_h::Mesh &mesh, AnalyticFunction func, ShapeField field) {
field(edge, 0, i, MeshField::Edge) = func(Vec<2>{x, y});
}
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {mesh.nedges()},
+ MeshField::parallel_for({0}, {mesh.nedges()},
setFieldAtEdges, "setFieldAtEdges");
}
diff --git a/test/testSprThwaitesAdapt.cpp b/test/testSprThwaitesAdapt.cpp
index e170c36..61479fa 100644
--- a/test/testSprThwaitesAdapt.cpp
+++ b/test/testSprThwaitesAdapt.cpp
@@ -142,7 +142,7 @@ void setFieldAtVertices(Omega_h::Mesh &mesh, Reals recoveredStrain,
auto setFieldAtVertices = KOKKOS_LAMBDA(const int &vtx) {
field(vtx, 0, 0, MeshField::Vertex) = recoveredStrain[vtx];
};
- MeshField::parallel_for(ExecutionSpace(), {0}, {mesh.nverts()},
+ MeshField::parallel_for({0}, {mesh.nverts()},
setFieldAtVertices, "setFieldAtVertices");
}