23#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
47static Eigen::Matrix<t_real, 3, Eigen::Dynamic> NodeCoords(
ElemType t)
56 std::uniform_real_distribution<t_real> u01(0.05, 0.95);
57 std::uniform_real_distribution<t_real> um11(-0.8, 0.8);
65 return MakePoint(um11(rng));
69 t_real xi = u01(rng) * 0.5;
70 t_real et = u01(rng) * (1.0 - xi) * 0.8;
71 return MakePoint(xi, et);
74 return MakePoint(um11(rng), um11(rng));
77 t_real xi = u01(rng) * 0.3;
78 t_real et = u01(rng) * (1.0 - xi) * 0.3;
79 t_real zt = u01(rng) * (1.0 - xi - et) * 0.8;
80 return MakePoint(xi, et, zt);
83 return MakePoint(um11(rng), um11(rng), um11(rng));
86 t_real xi = u01(rng) * 0.5;
87 t_real et = u01(rng) * (1.0 - xi) * 0.8;
89 return MakePoint(xi, et, zt);
94 t_real zt = u01(rng) * 0.8;
95 t_real scale = (1.0 - zt) * 0.8;
96 t_real xi = um11(rng) * scale;
97 t_real et = um11(rng) * scale;
98 return MakePoint(xi, et, zt);
106static constexpr ElemType AllTypes[] = {
112static constexpr ElemType Elem2DWithFaces[] = {
116static constexpr ElemType Elem3DWithFaces[] = {
120static constexpr ElemType O1Types[] = {
124static constexpr ElemType O2Types[] = {
131TEST_CASE(
"ElementTraits: basic identification fields are consistent")
133 for (
auto t : AllTypes)
139 CHECK(e.GetDim() >= 1);
140 CHECK(e.GetDim() <= 3);
141 CHECK(e.GetNumNodes() >= e.GetNumVertices());
142 CHECK(e.GetNumVertices() >= 2);
145 CHECK((e.GetOrder() == 1 || e.GetOrder() == 2));
152TEST_CASE(
"ElementTraits: standard coordinates have correct dimensions")
154 for (
auto t : AllTypes)
156 auto coords = NodeCoords(t);
161 CHECK(coords.rows() == 3);
164 CHECK(coords.cols() == e.GetNumNodes());
171 CHECK(coords(1,
i) == doctest::Approx(0.0));
172 CHECK(coords(2,
i) == doctest::Approx(0.0));
181 CHECK(coords(2,
i) == doctest::Approx(0.0));
187TEST_CASE(
"ElementTraits: face definitions are consistent for 2D elements")
189 for (
auto t : Elem2DWithFaces)
194 int numFaces = e.GetNumFaces();
197 for (
int iFace = 0; iFace < numFaces; iFace++)
210TEST_CASE(
"ElementTraits: face definitions are consistent for 3D elements")
212 for (
auto t : Elem3DWithFaces)
217 int numFaces = e.GetNumFaces();
220 for (
int iFace = 0; iFace < numFaces; iFace++)
233TEST_CASE(
"ElementTraits: ExtractFaceNodes works correctly")
238 std::vector<DNDS::index> nodes = {0, 1, 2};
239 std::array<DNDS::index, 2> faceNodes;
243 CHECK(faceNodes[0] == 0);
244 CHECK(faceNodes[1] == 1);
247 e.ExtractFaceNodes(1, nodes, faceNodes);
248 CHECK(faceNodes[0] == 1);
249 CHECK(faceNodes[1] == 2);
252 e.ExtractFaceNodes(2, nodes, faceNodes);
253 CHECK(faceNodes[0] == 2);
254 CHECK(faceNodes[1] == 0);
260 std::vector<DNDS::index> nodes = {0, 1, 2, 3};
261 std::array<DNDS::index, 2> faceNodes;
264 for (
int i = 0;
i < 4;
i++)
268 CHECK(faceNodes[0] < 4);
269 CHECK(faceNodes[1] < 4);
274TEST_CASE(
"ElementTraits: order elevation data is consistent for O1 elements")
277 for (
auto t : O1Types)
294 CHECK(e.GetNumElev_O1O2() > 0);
298TEST_CASE(
"ElementTraits: order elevation data for specific elements")
304 CHECK(e.GetNumElev_O1O2() == 1);
314 CHECK(e.ObtainElevatedElem().type ==
Tri6);
315 CHECK(e.GetNumElev_O1O2() == 3);
322 CHECK(e.GetNumElev_O1O2() == 5);
329 CHECK(e.GetNumElev_O1O2() == 19);
333TEST_CASE(
"ElementTraits: O2 elements do not have further elevation")
336 for (
auto t : O2Types)
343 CHECK(e.GetNumElev_O1O2() == 0);
347TEST_CASE(
"ElementTraits: ExtractElevNodeSpanNodes works correctly")
352 std::vector<DNDS::index> nodes = {0, 1, 2};
353 std::array<DNDS::index, 2> spanNodes;
356 for (
int i = 0;
i < e.GetNumElev_O1O2();
i++)
359 CHECK(spanNodes[0] < 3);
360 CHECK(spanNodes[1] < 3);
365TEST_CASE(
"ElementTraits: bisection data is valid for O2 elements")
370 CHECK(e.GetO2NumBisect() == 2);
373 for (
int i = 0;
i < e.GetO2NumBisect();
i++)
382 CHECK(e.GetO2NumBisect() == 4);
383 for (
int i = 0;
i < e.GetO2NumBisect();
i++)
392 CHECK(e.GetO2NumBisect() == 8);
393 for (
int i = 0;
i < e.GetO2NumBisect();
i++)
402 CHECK(e.GetO2NumBisect() == 8);
403 for (
int i = 0;
i < e.GetO2NumBisect();
i++)
412 CHECK(e.GetO2NumBisect() == 8);
413 for (
int i = 0;
i < e.GetO2NumBisect();
i++)
420TEST_CASE(
"ElementTraits: VTK conversion works correctly")
423 for (
auto t : AllTypes)
429 std::vector<t_real> nodes(e.GetNumNodes());
430 for (
int i = 0;
i < e.GetNumNodes();
i++)
437 CHECK(vtkCellType > 0);
440 CHECK(vtkNodes.size() <= e.GetNumNodes());
441 CHECK(vtkNodes.size() > 0);
445TEST_CASE(
"ElementTraits: VTK node order is a valid permutation for simple elements")
450 for (
size_t i = 0;
i < 2;
i++)
451 seen.insert(traits.vtkNodeOrder[
i]);
452 CHECK(seen.size() == 2);
453 CHECK(*seen.begin() == 0);
454 CHECK(*seen.rbegin() == 1);
459 for (
size_t i = 0;
i < 3;
i++)
460 seen.insert(traits.vtkNodeOrder[
i]);
461 CHECK(seen.size() == 3);
463 CHECK(traits.vtkNodeOrder[0] == 0);
464 CHECK(traits.vtkNodeOrder[1] == 2);
465 CHECK(traits.vtkNodeOrder[2] == 1);
470 for (
size_t i = 0;
i < 6;
i++)
471 seen.insert(traits.vtkNodeOrder[
i]);
472 CHECK(seen.size() == 6);
477 for (
size_t i = 0;
i < 8;
i++)
478 seen.insert(traits.vtkNodeOrder[
i]);
479 CHECK(seen.size() == 8);
480 CHECK(traits.vtkCellType == 12);
490 constexpr int nTrials = 10;
491 std::mt19937 rng(42);
493 for (
auto t : AllTypes)
497 for (
int trial = 0; trial < nTrials; trial++)
499 auto p = RandomInteriorPoint(t, rng);
504 CHECK(sum == doctest::Approx(1.0).epsilon(1e-12));
509TEST_CASE(
"Shape functions: nodal interpolation (Kronecker delta)")
511 for (
auto t : AllTypes)
514 auto coords = NodeCoords(t);
535TEST_CASE(
"Shape functions: D1 derivatives vs finite difference")
537 constexpr int nTrials = 5;
539 constexpr t_real tol = 1e-5;
540 std::mt19937 rng(123);
542 for (
auto t : AllTypes)
546 int dim = e.GetDim();
549 for (
int trial = 0; trial < nTrials; trial++)
551 auto p = RandomInteriorPoint(t, rng);
557 for (
int d = 0; d < dim; d++)
569 t_real fd = (Np(0,
j) - Nm(0,
j)) / (2 * h);
573 CHECK(an == doctest::Approx(fd).epsilon(tol));
580TEST_CASE(
"Shape functions: D2 derivatives vs finite difference of D1")
583 constexpr t_real tol = 1e-3;
584 std::mt19937 rng(456);
586 for (
auto t : AllTypes)
590 int dim = e.GetDim();
593 auto p = RandomInteriorPoint(t, rng);
596 e.GetDiNj(
p, DiNj, 2);
599 for (
int d = 0; d < dim; d++)
611 t_real fd_d2 = (D1p(d,
j) - D1m(d,
j)) / (2 * h);
618 row = 3 + (d == 0 ? 0 : 2);
625 CHECK(an == doctest::Approx(fd_d2).epsilon(tol));
631TEST_CASE(
"Shape functions: all derivatives are finite")
634 std::mt19937 rng(789);
636 for (
auto t : AllTypes)
639 auto p = RandomInteriorPoint(t, rng);
643 e.GetDiNj(
p, DiNj, 3);
650 CHECK(std::isfinite(DiNj(
i,
j)));
679 auto coords = NodeCoords(t);
681 tPoint c = tPoint::Zero();
682 for (
int i = 0;
i < e.GetNumVertices();
i++)
684 c /= e.GetNumVertices();
689static Eigen::Matrix<t_real, 3, Eigen::Dynamic> FaceCoords(
692 auto allCoords = NodeCoords(cellType);
697 std::array<DNDS::index, 10> faceNodeIdx;
698 Eigen::Array<DNDS::index, Eigen::Dynamic, 1> localIdx(cell.GetNumNodes());
699 for (
int i = 0;
i < cell.GetNumNodes();
i++)
702 cell.ExtractFaceNodes(iFace, localIdx, faceNodeIdx);
704 Eigen::Matrix<t_real, 3, Eigen::Dynamic> fc(3, nFaceNodes);
705 for (
int i = 0;
i < nFaceNodes;
i++)
706 fc.col(
i) = allCoords.col(faceNodeIdx[
i]);
713static tPoint ExpectedNormal2D(
714 const Eigen::Matrix<t_real, 3, Eigen::Dynamic> &fc,
721 n << tangent(1), -tangent(0), 0.0;
724 tPoint faceMid = (v0 + v1) * 0.5;
725 if (
n.dot(faceMid - centroid) < 0)
728 return n.normalized();
734static tPoint ExpectedNormal3D(
735 const Eigen::Matrix<t_real, 3, Eigen::Dynamic> &fc,
741 tPoint n = (v1 - v0).cross(v2 - v0);
745 tPoint faceMid = (v0 + v1 + v2) / 3.0;
746 if (
n.dot(faceMid - centroid) < 0)
749 return n.normalized();
752TEST_CASE(
"Face normals: 2D elements match expected unit normal at quad points")
756 for (
auto t : Elem2DWithFaces)
759 tPoint centroid = CellCentroid(t);
762 for (
int iFace = 0; iFace < cell.GetNumFaces(); iFace++)
765 auto fc = FaceCoords(t, iFace);
766 tPoint expectedN = ExpectedNormal2D(fc, centroid);
771 for (
int iG = 0; iG < quad.GetNumPoints(); iG++)
773 auto [pParam, w] = quad.GetQuadraturePointInfo(iG);
781 tPoint tangent = J.col(0);
783 computedN << tangent(1), -tangent(0), 0.0;
784 tPoint computedUnitN = computedN.normalized();
788 CHECK(computedUnitN(0) == doctest::Approx(expectedN(0)).epsilon(tol));
789 CHECK(computedUnitN(1) == doctest::Approx(expectedN(1)).epsilon(tol));
790 CHECK(computedUnitN(2) == doctest::Approx(expectedN(2)).epsilon(tol));
794 t_real outwardDot = computedN.dot(pPhys - centroid);
795 CHECK(outwardDot > 0.0);
798 t_real jdet = JacobiDetFace<2>(J);
803 tPoint edgeVec = fc.col(1) - fc.col(0);
804 t_real expectedJdet = edgeVec.norm() / 2.0;
805 CHECK(jdet == doctest::Approx(expectedJdet).epsilon(tol));
811TEST_CASE(
"Face normals: 3D elements match expected unit normal at quad points")
815 for (
auto t : Elem3DWithFaces)
818 tPoint centroid = CellCentroid(t);
821 for (
int iFace = 0; iFace < cell.GetNumFaces(); iFace++)
824 auto fc = FaceCoords(t, iFace);
825 tPoint expectedN = ExpectedNormal3D(fc, centroid);
830 for (
int iG = 0; iG < quad.GetNumPoints(); iG++)
832 auto [pParam, w] = quad.GetQuadraturePointInfo(iG);
838 tPoint computedN = J.col(0).cross(J.col(1));
839 t_real computedMag = computedN.norm();
840 tPoint computedUnitN = computedN / computedMag;
845 CHECK(computedUnitN(0) == doctest::Approx(expectedN(0)).epsilon(tol));
846 CHECK(computedUnitN(1) == doctest::Approx(expectedN(1)).epsilon(tol));
847 CHECK(computedUnitN(2) == doctest::Approx(expectedN(2)).epsilon(tol));
851 t_real outwardDot = computedN.dot(pPhys - centroid);
852 CHECK(outwardDot > 0.0);
855 t_real jdet = JacobiDetFace<3>(J);
857 CHECK(jdet == doctest::Approx(computedMag).epsilon(tol));
863TEST_CASE(
"Face normals: CGNS outward convention (no centroid correction needed)")
873 for (
auto t : Elem2DWithFaces)
876 tPoint centroid = CellCentroid(t);
879 for (
int iFace = 0; iFace < cell.GetNumFaces(); iFace++)
881 auto fc = FaceCoords(t, iFace);
882 tPoint v0 = fc.col(0), v1 = fc.col(1);
885 rawNormal << tangent(1), -tangent(0), 0.0;
887 tPoint faceMid = (v0 + v1) * 0.5;
888 t_real dot = rawNormal.dot(faceMid - centroid);
894 for (
auto t : Elem3DWithFaces)
897 tPoint centroid = CellCentroid(t);
900 for (
int iFace = 0; iFace < cell.GetNumFaces(); iFace++)
902 auto fc = FaceCoords(t, iFace);
903 tPoint v0 = fc.col(0), v1 = fc.col(1), v2 = fc.col(2);
904 tPoint rawNormal = (v1 - v0).cross(v2 - v0);
907 tPoint faceMid = (v0 + v1 + v2) / 3.0;
908 t_real dot = rawNormal.dot(faceMid - centroid);
915TEST_CASE(
"Edge topology: 3D elements have correct edge count and types")
917 for (
auto t : Elem3DWithFaces)
922 int numEdges = cell.GetNumEdges();
925 for (
int iEdge = 0; iEdge < numEdges; iEdge++)
938TEST_CASE(
"Edge topology: extracted edge nodes are valid cell nodes")
940 for (
auto t : Elem3DWithFaces)
943 auto allCoords = NodeCoords(t);
947 std::vector<DNDS::index> localIdx(cell.GetNumNodes());
948 for (
int i = 0;
i < cell.GetNumNodes();
i++)
951 for (
int iEdge = 0; iEdge < cell.GetNumEdges(); iEdge++)
954 std::array<DNDS::index, 3> edgeNodeIdx = {-1, -1, -1};
961 CHECK(edgeNodeIdx[
i] >= 0);
962 CHECK(edgeNodeIdx[
i] < cell.GetNumNodes());
966 CHECK(edgeNodeIdx[0] != edgeNodeIdx[1]);
969 tPoint p0 = allCoords.col(edgeNodeIdx[0]);
970 tPoint p1 = allCoords.col(edgeNodeIdx[1]);
971 CHECK((p1 - p0).norm() > 0.0);
976 tPoint pMid = allCoords.col(edgeNodeIdx[2]);
984TEST_CASE(
"Edge topology: edges are unique (no duplicate edges per cell)")
986 for (
auto t : Elem3DWithFaces)
991 std::vector<DNDS::index> localIdx(cell.GetNumNodes());
992 for (
int i = 0;
i < cell.GetNumNodes();
i++)
996 std::set<std::pair<DNDS::index, DNDS::index>> edgeSet;
997 for (
int iEdge = 0; iEdge < cell.GetNumEdges(); iEdge++)
999 std::array<DNDS::index, 3> edgeNodeIdx = {-1, -1, -1};
1000 cell.ExtractEdgeNodes(iEdge, localIdx, edgeNodeIdx);
1002 auto v0 = std::min(edgeNodeIdx[0], edgeNodeIdx[1]);
1003 auto v1 = std::max(edgeNodeIdx[0], edgeNodeIdx[1]);
1004 auto result = edgeSet.insert({v0, v1});
1008 CHECK(
static_cast<int>(edgeSet.size()) == cell.GetNumEdges());
Eigen::Matrix< t_real, Eigen::Dynamic, Eigen::Dynamic > tDiNj
Eigen::Matrix< t_real, 3, Eigen::Dynamic > tD1Nj
tJacobi ShapeJacobianCoordD01Nj(const tCoordsIn &cs, Eigen::Ref< const tD01Nj > DiNj)
DNDS_DEVICE_CALLABLE constexpr decltype(auto) DispatchElementType(ElemType t, Func &&func)
Static dispatch over element types at compile time.
DNDS_DEVICE_CALLABLE constexpr t_real ParamSpaceVolume(ParamSpace ps)
Get the volume of a parametric space.
Eigen::Matrix< t_real, 4, Eigen::Dynamic > tD01Nj
std::pair< int, std::vector< index > > ToVTKVertsAndData(Element e, const TIn &vin)
Eigen::Matrix< t_real, 3, Eigen::Dynamic > GetStandardCoord(ElemType t)
Eigen::RowVector< t_real, Eigen::Dynamic > tNj
tPoint PPhysicsCoordD01Nj(const tCoordsIn &cs, Eigen::Ref< const tD01Nj > DiNj)
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
DNDS_DEVICE_CALLABLE constexpr Element ObtainEdge(t_index iEdge) const
Edge sub-element type for edge iEdge (Line2 or Line3).
DNDS_DEVICE_CALLABLE constexpr ParamSpace GetParamSpace() const
constexpr Element ObtainElevNodeSpan(t_index iNodeElev) const
void ExtractEdgeNodes(t_index iEdge, const TIn &nodes, TOut &edgeNodesOut)
DNDS_DEVICE_CALLABLE constexpr Element ObtainFace(t_index iFace) const
constexpr Element ObtainElevatedElem() const
void GetD01Nj(const tPoint &pParam, tD01Nj &D01Nj) const
void ExtractFaceNodes(t_index iFace, const TIn &nodes, TOut &faceNodesOut)
DNDS_DEVICE_CALLABLE constexpr t_index GetDim() const
void ExtractElevNodeSpanNodes(t_index iNodeElev, const TIn &nodes, TOut &spanNodes)
DNDS_DEVICE_CALLABLE constexpr t_index GetNumNodes() const
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")
const tPoint const tPoint const tPoint & p
Eigen::Vector3d n(1.0, 0.0, 0.0)