Skip to content

Comments

Reduced Quintic Implicit#77

Draft
jvmespark wants to merge 12 commits intoSCOREC:mainfrom
jvmespark:reduced_quintic_implicit
Draft

Reduced Quintic Implicit#77
jvmespark wants to merge 12 commits intoSCOREC:mainfrom
jvmespark:reduced_quintic_implicit

Conversation

@jvmespark
Copy link

No description provided.

Copy link
Contributor

@cwsmith cwsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @jvmespark .

In addition to the comments below, I have a a few requests:

  • Please add a description in the PR. If you got the shape function/gradient/quadrature points from literature please reference it.
  • The CMakeLists.txt file needs to be modified to add the new test.
  • Please add a test to exercise evaluate(...) (given user defined parametric coords in each element, return the field value at those locations). As with the integration test, I'd strongly prefer a test that we know the result for and can check against it (as opposed to a simpler 'it doesn't crash' test).

const auto family = OMEGA_H_SIMPLEX;
auto len = 1.0;
return Omega_h::build_box(world, family, len, len, len,
2, 2, (dim == 3 ? 2 : 0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the template dim can be removed and the third dimension hardcoded as zero.

{
auto mesh2D = createMesh<2>(lib);
MeshField::OmegahMeshField<ExecutionSpace, 2, MeshField::KokkosController> omf2D(mesh2D);
runReducedQuinticImplicit<MeshField::KokkosController>(mesh2D, omf2D);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an expected result from the integration? I'd much prefer a test that we know the result for.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is probably some test cases in M3DC1 that I can dig up and share with James.

0.1167862757264 / 2.0),
IntegrationPoint(Vector3{0.249286745, 0.249286745, 0.501426509},
0.1167862757264 / 2.0),

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of the points here do not have enough precision and fail summing to 1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jvmespark This looks like it is the standard Gauss-Legendre points. You can copy them from here with more precision: https://github.com/SCOREC/core/blob/6a972af6daceec626e589b4d4f63acc22e27317f/apf/apfIntegrate.cc#L216

Alternatively, it looks like we may be able to use this script in APF to calculate to machine precision: https://github.com/SCOREC/core/blob/6a972af6daceec626e589b4d4f63acc22e27317f/apf/apfPolyBasis1D.cc

const auto [shp, map] = MeshField::Omegah::getReducedQuinticImplicitElement(mesh);
MeshField::FieldElement fes(mesh.nelems(), field, shp, map);

ReducedQuinticImplicitIntegrator<FieldElement> integrator(fes);
Copy link
Collaborator

@Joshua-Kloepfer Joshua-Kloepfer Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ReducedQuinticImplicitIntegrator<FieldElement> integrator(fes);
ReducedQuinticImplicitIntegrator integrator(fes);

Either do not put in template or you have to include the templating of FieldElement. ie MeshField::FieldElement<templating>.

@Joshua-Kloepfer
Copy link
Collaborator

Can you add
meshfields_add_exe(ReducedQuinticImplicitIntegratorTest test/testReducedQuinticImplicitIntegrator.cpp)
test_func(ReducedQuinticImplicitIntegratorTest ./ReducedQuinticImplicitIntegratorTest)
to CMakeLists.txt

};

template <int ShapeOrder> auto getTriangleElement(Omega_h::Mesh &mesh) {
static_assert(ShapeOrder == 1 || ShapeOrder == 2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static_assert(ShapeOrder == 1 || ShapeOrder == 2);
static_assert(ShapeOrder == 1 || ShapeOrder == 2 || ShapeOrder == 5);

@jacobmerson
Copy link
Contributor

jacobmerson commented Nov 7, 2025

@cwsmith @Joshua-Kloepfer One thing that we noticed is that in core the integration only stores n-1 coordinates where n is the number of vertices. This is why Vector3 can be used for Tets (it has 4 parametric coordinates). Do we want to make the integration point definitions consistent between triangles/tets? If so, we should either ignore the last coordinate and compute it with $$\xi_n = 1-\sum_{i=1}^{num nodes-1} \xi_i$$ or the storage needs to be able to accommodate 4 entries.

@Joshua-Kloepfer
Copy link
Collaborator

@jacobmerson Currently in our code, we store all n coordinates so for Tets we decided to store all 4 parametric coordinates (https://github.com/SCOREC/meshFields/blob/jk/FixIntegration/src/MeshField_Integrate.hpp#L85) and in triangles we store 3 (https://github.com/SCOREC/meshFields/blob/jk/FixIntegration/src/MeshField_Integrate.hpp#L85). We felt that this was the right approach. Is there somewhere else where we are not storing all the entries?

@jvmespark jvmespark marked this pull request as draft November 14, 2025 11:47
auto& rqShape = element.shp;

MeshField::Vector3 xi = {1.0/3.0, 1.0/3.0, 1.0/3.0};
constexpr int nn = static_cast<int>(MeshField::ReducedQuinticImplicitShape::numNodes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this require a static cast to int?

auto dN = rqShape.getLocalGradients(xi);
// Evaluate shape functions and local gradients at the centroid.
const auto N = rqShape.getValues(xi);
(void)rqShape.getLocalGradients(xi); // computed for coverage; not asserted here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? what do you mean here?

for (int i = 0; i < nn; ++i) {
sumN += N[i];
}
for (int i = 0; i < nn; ++i) sumN += static_cast<double>(N[i]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you static casting to double? Since this is on CPU, you can use std::accumulate

printf("[ReducedQuinticImplicit] Integrated Value = %.12e\n", total);
Kokkos::View<MeshField::Real *> /*dV*/) override
{
const int npts = static_cast<int>(w.extent(0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use const auto here? why do you need to cast to int?

"Integrate_f_bary_to_phys", npts,
KOKKOS_LAMBDA(const int ip, double& acc) {
// Barycentric weights (l0,l1,l2) corresponding to the element's vertices (v0,v1,v2).
const double l0 = static_cast<double>(p(ip, 0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect the result of p to be double? why is static cast neeeded? Use const auto instead

const double y = l0 * y0 + l1 * y1 + l2 * y2;

// Accumulate f(x,y) * w. We assume w already includes dV.
acc += f_analytic(x, y) * static_cast<double>(w(ip)); // w includes dV
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static_cast?

Kokkos::parallel_reduce(
"Integrate_1_bary_to_phys", npts,
KOKKOS_LAMBDA(const int ip, double& acc) {
acc += static_cast<double>(w(ip));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static_cast?

Comment on lines 270 to 281
if (triNodeIdx == 0) {
osh_ent = triVerts[3*tri + 0];
nodeInHolder = 0;
}
else if (triNodeIdx == 1) {
osh_ent = triVerts[3*tri + 1];
nodeInHolder = 0;
}
else if (triNodeIdx == 2) {
osh_ent = triVerts[3*tri + 2];
nodeInHolder = 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't all of this be one statement? i.e., osh_ent = triVerts[3*tri + triNodeIdx]

}
else if (triNodeIdx >= 3 && triNodeIdx <= 6) {
const MeshField::LO v0 = triVerts[3*tri + 0];
const MeshField::LO v1 = triVerts[3*tri + 1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed in this branch of the if statement?

Comment on lines 235 to 240
auto fact = [](int n) {
double r = 1.0;
for (int i = 2; i <= n; ++i) r *= double(i);
return r;
};
const double f5 = fact(5);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need to do this you should use an IIFE, don't bother naming the lambda.

for (int i = 0; i <= 5; ++i) {
for (int j = 0; j <= 5 - i; ++j) {
int k = 5 - i - j;
double coeff = f5 / (fact(i) * fact(j) * fact(k));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this calculation needs to be explained in the comment. One issue I see is that you are recomputing the factorials many times. Would it make more sense to have a table of the coefficients?

Comment on lines 271 to 276
auto fact = [](int n) {
double r = 1.0;
for (int i = 2; i <= n; ++i) r *= double(i);
return r;
};
const double f5 = fact(5);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have you repeated the code?

Omega_h::Mesh mesh(&lib);
Omega_h::build_from_elems_and_coords(&mesh, OMEGA_H_SIMPLEX, 2, verts, coords);

runTest<MeshField::KokkosController>(mesh);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have run test return a bool or error code that you can use to set the return value of main. This can be used with ctest to know if the test passed/failed.

}

std::cout << "[PASS] ReducedQuinticImplicit evaluator test (partition of unity)\n";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are missing a test case where you evaluate the shape function for example at 1/3, 1/3, 1/3 for up to a 4th order polynomial. You should be able to get an exact value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants