544 lines
19 KiB
C++
544 lines
19 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
|
|
#include "mfem.hpp"
|
|
#include "unit_tests.hpp"
|
|
|
|
using namespace mfem;
|
|
|
|
static bool testQuadratureInterpolator(const int dim,
|
|
const int p,
|
|
const int qpts,
|
|
const QVectorLayout q_layout,
|
|
const int nx, const int ny, const int nz)
|
|
{
|
|
// Keep for debugging purposes:
|
|
if (verbose_tests)
|
|
{
|
|
std::cout << "testQuadratureInterpolator(dim=" << dim
|
|
<< ",p=" << p
|
|
<< ",q=" << qpts
|
|
<< ",l=" << (q_layout == QVectorLayout::byNODES ?
|
|
"by_nodes" : "by_vdim")
|
|
<< ",nx=" << nx
|
|
<< ",ny=" << ny
|
|
<< ",nz=" << nz
|
|
<< ")" << std::endl;
|
|
}
|
|
|
|
const int vdim = dim;
|
|
const int seed = 0x100001b3;
|
|
const int ordering = Ordering::byNODES;
|
|
|
|
Mesh mesh = dim == 1 ? Mesh::MakeCartesian1D(nx, Element::SEGMENT) :
|
|
dim == 2 ? Mesh::MakeCartesian2D(nx,ny, Element::QUADRILATERAL) :
|
|
Mesh::MakeCartesian3D(nx,nx,nz, Element::HEXAHEDRON);
|
|
|
|
const H1_FECollection fec(p, dim);
|
|
FiniteElementSpace sfes(&mesh, &fec, 1, ordering);
|
|
FiniteElementSpace vfes(&mesh, &fec, vdim, ordering);
|
|
|
|
GridFunction x(&sfes);
|
|
x.Randomize(seed);
|
|
|
|
GridFunction nodes(&vfes);
|
|
mesh.SetNodalGridFunction(&nodes);
|
|
{
|
|
Array<int> dofs, vdofs;
|
|
GridFunction rdm(&vfes);
|
|
Vector h0(vfes.GetNDofs());
|
|
rdm.Randomize(seed);
|
|
rdm -= 0.5;
|
|
h0 = infinity();
|
|
for (int i = 0; i < mesh.GetNE(); i++)
|
|
{
|
|
vfes.GetElementDofs(i, dofs);
|
|
const real_t hi = mesh.GetElementSize(i);
|
|
for (int j = 0; j < dofs.Size(); j++)
|
|
{
|
|
h0(dofs[j]) = std::min(h0(dofs[j]), hi);
|
|
}
|
|
}
|
|
rdm.HostReadWrite();
|
|
for (int i = 0; i < vfes.GetNDofs(); i++)
|
|
{
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
rdm(vfes.DofToVDof(i,d)) *= (0.25/p)*h0(i);
|
|
}
|
|
}
|
|
for (int i = 0; i < vfes.GetNBE(); i++)
|
|
{
|
|
vfes.GetBdrElementVDofs(i, vdofs);
|
|
for (int j = 0; j < vdofs.Size(); j++) { rdm(vdofs[j]) = 0.0; }
|
|
}
|
|
nodes -= rdm;
|
|
}
|
|
|
|
const Geometry::Type GeomType = mesh.GetTypicalElementGeometry();
|
|
const IntegrationRule &ir = IntRules.Get(GeomType, 2*qpts-1);
|
|
const QuadratureInterpolator *sqi(sfes.GetQuadratureInterpolator(ir));
|
|
const QuadratureInterpolator *vqi(vfes.GetQuadratureInterpolator(ir));
|
|
|
|
const int NE(mesh.GetNE());
|
|
const int NQ(ir.GetNPoints());
|
|
const int ND(sfes.GetTypicalFE()->GetDof());
|
|
REQUIRE(ND == vfes.GetTypicalFE()->GetDof());
|
|
|
|
const ElementDofOrdering nat_ordering = ElementDofOrdering::NATIVE;
|
|
const ElementDofOrdering lex_ordering = ElementDofOrdering::LEXICOGRAPHIC;
|
|
const Operator *SRN(sfes.GetElementRestriction(nat_ordering));
|
|
const Operator *SRL(sfes.GetElementRestriction(lex_ordering));
|
|
const Operator *VRN(vfes.GetElementRestriction(nat_ordering));
|
|
const Operator *VRL(vfes.GetElementRestriction(lex_ordering));
|
|
MFEM_VERIFY(SRN, "No element sn-restriction operator found!");
|
|
MFEM_VERIFY(SRL, "No element sl-restriction operator found!");
|
|
MFEM_VERIFY(VRN, "No element vn-restriction operator found!");
|
|
MFEM_VERIFY(VRL, "No element vl-restriction operator found!");
|
|
|
|
const real_t rel_tol = 1e-12;
|
|
|
|
{
|
|
// Scalar
|
|
sqi->SetOutputLayout(q_layout);
|
|
Vector xe(1*ND*NE);
|
|
REQUIRE(xe.Size() == SRN->Height());
|
|
REQUIRE(SRN->Height() == SRL->Height());
|
|
// Full results
|
|
Vector sq_val_f(NQ*NE), sq_der_f(dim*NQ*NE), sq_pdr_f(dim*NQ*NE);
|
|
// Tensor results
|
|
Vector sq_val_t(NQ*NE), sq_der_t(dim*NQ*NE), sq_pdr_t(dim*NQ*NE);
|
|
{
|
|
// Full
|
|
SRN->Mult(x, xe);
|
|
sqi->DisableTensorProducts();
|
|
|
|
sqi->Values(xe, sq_val_f);
|
|
|
|
sqi->Derivatives(xe, sq_der_f);
|
|
|
|
sqi->PhysDerivatives(xe, sq_pdr_f);
|
|
}
|
|
{
|
|
// Tensor
|
|
SRL->Mult(x, xe);
|
|
sqi->EnableTensorProducts();
|
|
|
|
sqi->Values(xe, sq_val_t);
|
|
|
|
sqi->Derivatives(xe, sq_der_t);
|
|
|
|
sqi->PhysDerivatives(xe, sq_pdr_t);
|
|
}
|
|
real_t norm, rel_error;
|
|
|
|
norm = sq_val_f.Normlinf();
|
|
sq_val_f -= sq_val_t;
|
|
rel_error = sq_val_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "sq_val rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
|
|
norm = sq_der_f.Normlinf();
|
|
sq_der_f -= sq_der_t;
|
|
rel_error = sq_der_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "sq_der rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
|
|
norm = sq_pdr_f.Normlinf();
|
|
sq_pdr_f -= sq_pdr_t;
|
|
rel_error = sq_pdr_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "sq_pdr rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
}
|
|
|
|
{
|
|
// Vector
|
|
vqi->SetOutputLayout(q_layout);
|
|
Vector ne(vdim*ND*NE);
|
|
REQUIRE(ne.Size() == VRN->Height());
|
|
REQUIRE(VRN->Height() == VRL->Height());
|
|
// Full results
|
|
Vector vq_val_f(dim*NQ*NE), vq_der_f(vdim*dim*NQ*NE),
|
|
vq_det_f(NQ*NE),
|
|
vq_pdr_f(vdim*dim*NQ*NE);
|
|
// Tensor results
|
|
Vector vq_val_t(dim*NQ*NE), vq_der_t(vdim*dim*NQ*NE),
|
|
vq_det_t(NQ*NE),
|
|
vq_pdr_t(vdim*dim*NQ*NE);
|
|
{
|
|
// Full
|
|
VRN->Mult(nodes, ne);
|
|
vqi->DisableTensorProducts();
|
|
|
|
vqi->Values(ne, vq_val_f);
|
|
|
|
vqi->Derivatives(ne, vq_der_f);
|
|
|
|
vqi->Determinants(ne, vq_det_f);
|
|
|
|
vqi->PhysDerivatives(ne, vq_pdr_f);
|
|
}
|
|
{
|
|
// Tensor
|
|
VRL->Mult(nodes, ne);
|
|
vqi->EnableTensorProducts();
|
|
|
|
vqi->Values(ne, vq_val_t);
|
|
|
|
vqi->Derivatives(ne, vq_der_t);
|
|
|
|
vqi->Determinants(ne, vq_det_t);
|
|
|
|
vqi->PhysDerivatives(ne, vq_pdr_t);
|
|
}
|
|
real_t norm, rel_error;
|
|
|
|
norm = vq_val_f.Normlinf();
|
|
vq_val_f -= vq_val_t;
|
|
rel_error = vq_val_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "vq_val rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
|
|
norm = vq_der_f.Normlinf();
|
|
vq_der_f -= vq_der_t;
|
|
rel_error = vq_der_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "vq_der rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
|
|
norm = vq_det_f.Normlinf();
|
|
vq_det_f -= vq_det_t;
|
|
rel_error = vq_det_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "vq_det rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
|
|
norm = vq_pdr_f.Normlinf();
|
|
vq_pdr_f -= vq_pdr_t;
|
|
rel_error = vq_pdr_f.Normlinf()/norm;
|
|
if (verbose_tests)
|
|
{ std::cout << "vq_pdr rel. error = " << rel_error << std::endl; }
|
|
REQUIRE(rel_error <= rel_tol);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_CASE("QuadratureInterpolator", "[QuadratureInterpolator][GPU]")
|
|
{
|
|
SECTION("H1 tensor elements: compare tensor and non-tensor evaluations")
|
|
{
|
|
const auto dim = GENERATE(1,2,3); // dimension
|
|
const auto p = GENERATE(range(1,7)); // element order, 1 <= p < 7
|
|
const auto q = GENERATE_COPY(p+1,p+2); // 1D quadrature points
|
|
const auto l = GENERATE(QVectorLayout::byNODES, QVectorLayout::byVDIM);
|
|
const auto nx = 3; // number of element in x
|
|
const auto ny = 3; // number of element in y
|
|
const auto nz = 3; // number of element in z
|
|
testQuadratureInterpolator(dim, p, q, l, nx, ny, nz);
|
|
}
|
|
|
|
SECTION("H1 elements: values and physical derivatives")
|
|
{
|
|
const auto mesh_fname = GENERATE(
|
|
"../../data/inline-segment.mesh",
|
|
"../../data/star.mesh",
|
|
"../../data/star-q3.mesh",
|
|
"../../data/square-disc-p2.mesh",
|
|
"../../data/fichera.mesh",
|
|
"../../data/fichera-q3.mesh",
|
|
"../../data/escher-p2.mesh",
|
|
"../../data/diag-segment-2d.mesh", // 1D mesh in 2D
|
|
"../../data/diag-segment-3d.mesh", // 1D mesh in 3D
|
|
"../../data/star-surf.mesh" // surface mesh
|
|
);
|
|
const int order = GENERATE(1, 2, 3);
|
|
CAPTURE(mesh_fname, order);
|
|
|
|
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
|
|
H1_FECollection fec(order);
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
QuadratureSpace qs(&mesh, 2*order);
|
|
|
|
GridFunction gf(&fes);
|
|
gf.Randomize(1);
|
|
|
|
const ElementDofOrdering ordering =
|
|
(mesh.Dimension() == 1 || mesh.MeshGenerator() == 2) ?
|
|
ElementDofOrdering::LEXICOGRAPHIC : ElementDofOrdering::NATIVE;
|
|
INFO("ordering: " << (ordering == ElementDofOrdering::NATIVE ?
|
|
"NATIVE" : "LEXICOGRAPHIC"));
|
|
// Use element restriction to go from L-vector to E-vector
|
|
const Operator *R = fes.GetElementRestriction(ordering);
|
|
Vector e_vec(R->Height());
|
|
R->Mult(gf, e_vec);
|
|
|
|
// Use quadrature interpolator to go from E-vector to Q-vector
|
|
const QuadratureInterpolator *qi =
|
|
fes.GetQuadratureInterpolator(qs);
|
|
qi->SetOutputLayout(QVectorLayout::byVDIM);
|
|
|
|
// Compare QuadratureInterpolator::VALUES evaluation vs
|
|
// GridFunction::GetValue():
|
|
{
|
|
INFO("evaluation: VALUES");
|
|
QuadratureFunction qf1(qs), qf2(qs);
|
|
|
|
const int ne = qs.GetNE();
|
|
Vector values;
|
|
for (int iel = 0; iel < ne; ++iel)
|
|
{
|
|
qf1.GetValues(iel, values);
|
|
const IntegrationRule &ir = qs.GetIntRule(iel);
|
|
ElementTransformation &T = *qs.GetTransformation(iel);
|
|
for (int iq = 0; iq < ir.Size(); ++iq)
|
|
{
|
|
const IntegrationPoint &ip = ir[iq];
|
|
T.SetIntPoint(&ip);
|
|
values[iq] = gf.GetValue(T, ip);
|
|
}
|
|
}
|
|
|
|
qi->Values(e_vec, qf2);
|
|
|
|
const real_t base_vals_norm = qf1.Normlinf();
|
|
REQUIRE(base_vals_norm > 0_r);
|
|
qf1 -= qf2;
|
|
const real_t rel_error_norm = qf1.Normlinf()/base_vals_norm;
|
|
REQUIRE(rel_error_norm == MFEM_Approx(0.0));
|
|
}
|
|
|
|
// Compare QuadratureInterpolator::PHYSICAL_DERIVATIVES evaluation vs
|
|
// GridFunction::GetGradient():
|
|
{
|
|
INFO("evaluation: PHYSICAL_DERIVATIVES");
|
|
const int sdim = mesh.SpaceDimension();
|
|
QuadratureFunction qf1(qs, sdim), qf2(qs, sdim);
|
|
|
|
const int ne = qs.GetNE();
|
|
DenseMatrix values;
|
|
Vector col;
|
|
for (int iel = 0; iel < ne; ++iel)
|
|
{
|
|
qf1.GetValues(iel, values);
|
|
const IntegrationRule &ir = qs.GetIntRule(iel);
|
|
ElementTransformation &T = *qs.GetTransformation(iel);
|
|
for (int iq = 0; iq < ir.Size(); ++iq)
|
|
{
|
|
const IntegrationPoint &ip = ir[iq];
|
|
T.SetIntPoint(&ip);
|
|
values.GetColumnReference(iq, col);
|
|
gf.GetGradient(T, col);
|
|
}
|
|
}
|
|
|
|
qi->PhysDerivatives(e_vec, qf2);
|
|
|
|
const real_t base_phys_der_norm = qf1.Normlinf();
|
|
REQUIRE(base_phys_der_norm > 0_r);
|
|
qf1 -= qf2;
|
|
const real_t rel_error_norm = qf1.Normlinf()/base_phys_der_norm;
|
|
REQUIRE(rel_error_norm == MFEM_Approx(0.0));
|
|
}
|
|
}
|
|
|
|
SECTION("H(div) elements: values, phys. values, phys. magnitudes")
|
|
{
|
|
// Only quad and hex elements are supported, for now:
|
|
const auto mesh_fname = GENERATE(
|
|
"../../data/star-q2.mesh",
|
|
"../../data/fichera-q2.mesh"
|
|
);
|
|
const int order = GENERATE(0, 1, 2);
|
|
CAPTURE(mesh_fname, order);
|
|
|
|
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
|
|
const int dim = mesh.Dimension();
|
|
RT_FECollection fec(order, dim);
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
QuadratureSpace qs(&mesh, 2*(order+1) + (dim-1));
|
|
|
|
GridFunction gf(&fes);
|
|
gf.Randomize(55370091);
|
|
|
|
const ElementDofOrdering ordering =
|
|
(mesh.Dimension() == 1 || mesh.MeshGenerator() == 2) ?
|
|
ElementDofOrdering::LEXICOGRAPHIC : ElementDofOrdering::NATIVE;
|
|
INFO("ordering: " << (ordering == ElementDofOrdering::NATIVE ?
|
|
"NATIVE" : "LEXICOGRAPHIC"));
|
|
// Use element restriction to go from L-vector to E-vector
|
|
const Operator *R = fes.GetElementRestriction(ordering);
|
|
Vector e_vec(R->Height());
|
|
R->Mult(gf, e_vec);
|
|
|
|
// Use quadrature interpolator to go from E-vector to Q-vector
|
|
const QuadratureInterpolator *qi = fes.GetQuadratureInterpolator(qs);
|
|
// QuadratureFunctions use byVDIM ordering:
|
|
qi->SetOutputLayout(QVectorLayout::byVDIM);
|
|
|
|
QuadratureFunction qf_base_rv(qs, dim), qf_qi_rv(qs, dim); // ref vals
|
|
QuadratureFunction qf_base_pv(qs, dim), qf_qi_pv(qs, dim); // phys vals
|
|
QuadratureFunction qf_base_pm(qs, 1), qf_qi_pm(qs, 1); // phys magn
|
|
|
|
const int ne = qs.GetNE();
|
|
Array<int> vdofs;
|
|
Vector loc_data;
|
|
DenseMatrix vshape;
|
|
DenseMatrix vec_values;
|
|
Vector mag_values;
|
|
Vector col;
|
|
for (int iel = 0; iel < ne; ++iel)
|
|
{
|
|
const IntegrationRule &ir = qs.GetIntRule(iel);
|
|
ElementTransformation &T = *qs.GetTransformation(iel);
|
|
|
|
// reference values
|
|
qf_base_rv.GetValues(iel, vec_values); // dim x nqpts
|
|
{
|
|
const FiniteElement &fe = *fes.GetFE(iel);
|
|
const int dof = fe.GetDof();
|
|
fes.GetElementVDofs(iel, vdofs);
|
|
gf.GetSubVector(vdofs, loc_data);
|
|
vshape.SetSize(dof, dim);
|
|
const int nip = ir.GetNPoints();
|
|
vec_values.SetSize(dim, nip);
|
|
for (int j = 0; j < nip; j++)
|
|
{
|
|
const IntegrationPoint &ip = ir.IntPoint(j);
|
|
T.SetIntPoint(&ip);
|
|
fe.CalcVShape(ip, vshape);
|
|
vec_values.GetColumnReference(j, col);
|
|
vshape.MultTranspose(loc_data, col);
|
|
}
|
|
}
|
|
|
|
// physical values
|
|
qf_base_pv.GetValues(iel, vec_values);
|
|
gf.GetVectorValues(T, ir, vec_values);
|
|
|
|
// physical magnitudes
|
|
qf_base_pm.GetValues(iel, mag_values);
|
|
vec_values.Norm2(mag_values);
|
|
}
|
|
|
|
Vector empty;
|
|
qi->Values(e_vec, qf_qi_rv);
|
|
qi->Mult(e_vec, QuadratureInterpolator::PHYSICAL_VALUES,
|
|
qf_qi_pv, empty, empty);
|
|
qi->Mult(e_vec, QuadratureInterpolator::PHYSICAL_MAGNITUDES,
|
|
qf_qi_pm, empty, empty);
|
|
{
|
|
INFO("evaluation: VALUES");
|
|
const real_t base_norm = qf_base_rv.Normlinf();
|
|
REQUIRE(base_norm > 0_r);
|
|
qf_base_rv -= qf_qi_rv;
|
|
const real_t rel_error_norm = qf_base_rv.Normlinf()/base_norm;
|
|
REQUIRE(rel_error_norm == MFEM_Approx(0.0));
|
|
}
|
|
{
|
|
INFO("evaluation: PHYSICAL_VALUES");
|
|
const real_t base_norm = qf_base_pv.Normlinf();
|
|
REQUIRE(base_norm > 0_r);
|
|
qf_base_pv -= qf_qi_pv;
|
|
const real_t rel_error_norm = qf_base_pv.Normlinf()/base_norm;
|
|
REQUIRE(rel_error_norm == MFEM_Approx(0.0));
|
|
}
|
|
{
|
|
INFO("evaluation: PHYSICAL_MAGNITUDES");
|
|
const real_t base_norm = qf_base_pm.Normlinf();
|
|
REQUIRE(base_norm > 0_r);
|
|
qf_base_pm -= qf_qi_pm;
|
|
const real_t rel_error_norm = qf_base_pm.Normlinf()/base_norm;
|
|
REQUIRE(rel_error_norm == MFEM_Approx(0.0));
|
|
}
|
|
}
|
|
|
|
SECTION("Surface Determinants: 1D surface in 2D/3D and 2D surface in 3D")
|
|
{
|
|
const auto mesh_fname = GENERATE(
|
|
"../../data/diag-segment-2d.mesh", // 1D in 2D
|
|
"../../data/diag-segment-3d.mesh", // 1D in 3D
|
|
"../../data/star-surf.mesh" // 2D in 3D
|
|
);
|
|
|
|
// Using order > 1 to ensure curvature is used if supported by mesh
|
|
const int order = 3;
|
|
|
|
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
|
|
const int dim = mesh.Dimension();
|
|
const int sdim = mesh.SpaceDimension();
|
|
|
|
REQUIRE(dim < sdim);
|
|
|
|
// Ensure high-order curvature for non-trivial Jacobians where possible
|
|
mesh.SetCurvature(order);
|
|
|
|
const FiniteElementSpace *fes = mesh.GetNodalFESpace();
|
|
GridFunction *nodes = mesh.GetNodes();
|
|
|
|
// Quadrature space
|
|
QuadratureSpace qs(&mesh, 2*order);
|
|
const QuadratureInterpolator *qi = fes->GetQuadratureInterpolator(qs);
|
|
qi->SetOutputLayout(QVectorLayout::byVDIM);
|
|
|
|
// Prepare E-vector from nodes
|
|
const ElementDofOrdering ordering =
|
|
(mesh.Dimension() == 1 || mesh.MeshGenerator() == 2) ?
|
|
ElementDofOrdering::LEXICOGRAPHIC : ElementDofOrdering::NATIVE;
|
|
|
|
const Operator *R = fes->GetElementRestriction(ordering);
|
|
Vector e_vec(R->Height());
|
|
R->Mult(*nodes, e_vec);
|
|
|
|
// Compute determinants (weights) via QI
|
|
// Output vector size: qs.GetSize() * 1 (since determinant is scalar)
|
|
Vector q_det(qs.GetSize());
|
|
qi->Determinants(e_vec, q_det);
|
|
|
|
// Verify against ElementTransformation::Weight()
|
|
Vector q_weights(qs.GetSize());
|
|
const int ne = qs.GetNE();
|
|
int idx_counter = 0;
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
ElementTransformation *T = mesh.GetElementTransformation(i);
|
|
const IntegrationRule &ir = qs.GetIntRule(i);
|
|
for (int j = 0; j < ir.GetNPoints(); j++)
|
|
{
|
|
const IntegrationPoint &ip = ir.IntPoint(j);
|
|
T->SetIntPoint(&ip);
|
|
q_weights(idx_counter++) = T->Weight();
|
|
}
|
|
}
|
|
|
|
// Compare
|
|
Vector diff = q_det;
|
|
diff -= q_weights;
|
|
const real_t norm_w = q_weights.Normlinf();
|
|
const real_t norm_d = diff.Normlinf();
|
|
|
|
// If weights are effectively zero (e.g. degenerate), direct comparison might differ
|
|
// but for these valid meshes, weight should be > 0.
|
|
if (norm_w > 1e-12)
|
|
{
|
|
REQUIRE(norm_d / norm_w < 1e-12);
|
|
}
|
|
else
|
|
{
|
|
REQUIRE(norm_d < 1e-12);
|
|
}
|
|
}
|
|
}
|