peclet-core 1.3.1__tar.gz → 1.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {peclet_core-1.3.1 → peclet_core-1.3.2}/CITATION.cff +2 -2
  2. {peclet_core-1.3.1 → peclet_core-1.3.2}/CLAUDE.md +1 -1
  3. {peclet_core-1.3.1 → peclet_core-1.3.2}/PKG-INFO +1 -1
  4. {peclet_core-1.3.1 → peclet_core-1.3.2}/docs/Doxyfile +1 -1
  5. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/vof/curvature.hpp +46 -6
  6. {peclet_core-1.3.1 → peclet_core-1.3.2}/packaging/pyproject-core.toml +1 -1
  7. {peclet_core-1.3.1 → peclet_core-1.3.2}/pyproject.toml +1 -1
  8. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/CMakeLists.txt +8 -0
  9. peclet_core-1.3.2/tests/test_vof_pvfit.cpp +305 -0
  10. {peclet_core-1.3.1 → peclet_core-1.3.2}/.clang-format +0 -0
  11. {peclet_core-1.3.1 → peclet_core-1.3.2}/.clang-tidy +0 -0
  12. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  14. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/dependabot.yml +0 -0
  16. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/workflows/ci.yml +0 -0
  17. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/workflows/docs.yml +0 -0
  18. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/workflows/quality.yml +0 -0
  19. {peclet_core-1.3.1 → peclet_core-1.3.2}/.github/workflows/release.yml +0 -0
  20. {peclet_core-1.3.1 → peclet_core-1.3.2}/.gitignore +0 -0
  21. {peclet_core-1.3.1 → peclet_core-1.3.2}/CMakeLists.txt +0 -0
  22. {peclet_core-1.3.1 → peclet_core-1.3.2}/LICENSE +0 -0
  23. {peclet_core-1.3.1 → peclet_core-1.3.2}/README.md +0 -0
  24. {peclet_core-1.3.1 → peclet_core-1.3.2}/benchmarks/CMakeLists.txt +0 -0
  25. {peclet_core-1.3.1 → peclet_core-1.3.2}/benchmarks/bench_halo.cpp +0 -0
  26. {peclet_core-1.3.1 → peclet_core-1.3.2}/cmake/PecletCorePinMpiexec.cmake +0 -0
  27. {peclet_core-1.3.1 → peclet_core-1.3.2}/cmake/PecletCoreTest.cmake +0 -0
  28. {peclet_core-1.3.1 → peclet_core-1.3.2}/cmake/SuiteNanobind.cmake +0 -0
  29. {peclet_core-1.3.1 → peclet_core-1.3.2}/cmake/peclet-coreConfig.cmake.in +0 -0
  30. {peclet_core-1.3.1 → peclet_core-1.3.2}/docs/archive/README.md +0 -0
  31. {peclet_core-1.3.1 → peclet_core-1.3.2}/docs/archive/cuda-aware-mpi.md +0 -0
  32. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/cpu_budget.hpp +0 -0
  33. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/host_parallel.hpp +0 -0
  34. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/mpi.hpp +0 -0
  35. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/mpi_stub.hpp +0 -0
  36. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/portable.hpp +0 -0
  37. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/types.hpp +0 -0
  38. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/common/view.hpp +0 -0
  39. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/block_decomposer.hpp +0 -0
  40. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/block_indexer.hpp +0 -0
  41. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/gather_by_global_id.hpp +0 -0
  42. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/grid_redistribute.hpp +0 -0
  43. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/morton_indexer.hpp +0 -0
  44. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/redistribute_topology.hpp +0 -0
  45. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/stage_comm.hpp +0 -0
  46. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/decomp/stage_target.hpp +0 -0
  47. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/domain.hpp +0 -0
  48. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/field/field_set.hpp +0 -0
  49. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/body_properties.hpp +0 -0
  50. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/device_scene.hpp +0 -0
  51. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/grid_sdf.hpp +0 -0
  52. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/primitives.hpp +0 -0
  53. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/quadrature.hpp +0 -0
  54. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/scene.hpp +0 -0
  55. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/scene_builder.hpp +0 -0
  56. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/scene_query.hpp +0 -0
  57. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/sdf.hpp +0 -0
  58. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/geom/vti_io.hpp +0 -0
  59. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/grid_halo.hpp +0 -0
  60. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/grid_halo_topology.hpp +0 -0
  61. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/nbx.hpp +0 -0
  62. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/particle_halo.hpp +0 -0
  63. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/particle_halo_topology.hpp +0 -0
  64. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/particle_migrator.hpp +0 -0
  65. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/particle_migrator_view.hpp +0 -0
  66. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/halo/particle_rebalance.hpp +0 -0
  67. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/interp/particle_grid.hpp +0 -0
  68. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/python/kokkos_teardown.hpp +0 -0
  69. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/python/ndarray_interop.hpp +0 -0
  70. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/scheme/cut_cell_closure.hpp +0 -0
  71. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/scheme/ghost_closure.hpp +0 -0
  72. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/coloring.hpp +0 -0
  73. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/csr_bicgstab.hpp +0 -0
  74. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/csr_operator.hpp +0 -0
  75. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/face_csr.hpp +0 -0
  76. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/graph_amg.hpp +0 -0
  77. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/graph_amg_device.hpp +0 -0
  78. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/solver/vector_ops.hpp +0 -0
  79. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/vof/cutcell.hpp +0 -0
  80. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/vof/plic.hpp +0 -0
  81. {peclet_core-1.3.1 → peclet_core-1.3.2}/include/peclet/core/vof/wetting.hpp +0 -0
  82. {peclet_core-1.3.1 → peclet_core-1.3.2}/packaging/shell/core_geom.py +0 -0
  83. {peclet_core-1.3.1 → peclet_core-1.3.2}/packaging/shell/core_init.py +0 -0
  84. {peclet_core-1.3.1 → peclet_core-1.3.2}/packaging/shell/core_mpi.py +0 -0
  85. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/CMakeLists.txt +0 -0
  86. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/halo_bindings.cpp +0 -0
  87. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/packaging/_halo.pyi +0 -0
  88. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/packaging/halo_init.py +0 -0
  89. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/state_hash.py +0 -0
  90. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/state_hash_reference.json +0 -0
  91. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/test_mpi.py +0 -0
  92. {peclet_core-1.3.1 → peclet_core-1.3.2}/python/test_shell_deprecation.py +0 -0
  93. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/data/rcp_pack_seed3_unit.txt +0 -0
  94. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/python/CMakeLists.txt +0 -0
  95. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/python/interop_test_module.cpp +0 -0
  96. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/python/test_ndarray_interop.py +0 -0
  97. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_aligned_weighted.cpp +0 -0
  98. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_aligned_weighted_mpi.cpp +0 -0
  99. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_cpu_budget.cpp +0 -0
  100. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_csr_solver.cpp +0 -0
  101. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_cut_cell_closure.cpp +0 -0
  102. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_decomposition.cpp +0 -0
  103. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_diffusion.cpp +0 -0
  104. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_diffusion_sdf.cpp +0 -0
  105. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_domain.cpp +0 -0
  106. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_field_set.cpp +0 -0
  107. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_batch_device.cpp +0 -0
  108. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_body.cpp +0 -0
  109. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_owner.cpp +0 -0
  110. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_primitives.cpp +0 -0
  111. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_properties.cpp +0 -0
  112. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_quadrature.cpp +0 -0
  113. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_scene.cpp +0 -0
  114. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_scene_builder.cpp +0 -0
  115. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_scene_query.cpp +0 -0
  116. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_geom_tree_grad.cpp +0 -0
  117. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_ghost_particles_mpi.cpp +0 -0
  118. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_graph_amg.cpp +0 -0
  119. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_graph_amg_device.cpp +0 -0
  120. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_grid_halo.cpp +0 -0
  121. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_grid_halo_exchange.cpp +0 -0
  122. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_grid_redistribute_mpi.cpp +0 -0
  123. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_host_parallel.cpp +0 -0
  124. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_morton_indexer.cpp +0 -0
  125. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_nbx_rounds.cpp +0 -0
  126. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_grid.cpp +0 -0
  127. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_halo_exchange.cpp +0 -0
  128. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_halo_images_mpi.cpp +0 -0
  129. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_halo_mpi.cpp +0 -0
  130. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_migration.cpp +0 -0
  131. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_migrator_view_mpi.cpp +0 -0
  132. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_particle_rebalance.cpp +0 -0
  133. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_sdf.cpp +0 -0
  134. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_skip_mpi.hpp +0 -0
  135. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_stage_redistribute_mpi.cpp +0 -0
  136. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_stage_repartition_mpi.cpp +0 -0
  137. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_stage_target.cpp +0 -0
  138. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_util.hpp +0 -0
  139. {peclet_core-1.3.1 → peclet_core-1.3.2}/tests/test_vti.cpp +0 -0
  140. {peclet_core-1.3.1 → peclet_core-1.3.2}/tools/cuda_aware_mpi_check.cpp +0 -0
  141. {peclet_core-1.3.1 → peclet_core-1.3.2}/tools/cudampi-env.sh +0 -0
@@ -16,8 +16,8 @@ authors:
16
16
  orcid: "https://orcid.org/0000-0001-6099-3583"
17
17
  email: e.a.j.f.peters@tue.nl
18
18
  affiliation: "Eindhoven University of Technology"
19
- version: 1.3.1
20
- date-released: 2026-09-27
19
+ version: 1.3.2
20
+ date-released: 2026-10-03
21
21
  license: MIT
22
22
  doi: 10.5281/zenodo.21132435
23
23
  identifiers:
@@ -37,7 +37,7 @@ ctest --test-dir build --output-on-failure -LE bench # 76 ctests (77 with `ben
37
37
  export PATH=/usr/local/cuda-13.2/bin:$PATH # if the Kokkos install targets the CUDA backend
38
38
  cmake -S . -B build_kokkos -DPECLET_CORE_ENABLE_KOKKOS=ON \
39
39
  -DCMAKE_PREFIX_PATH=../extern/install/nvidia-cuda
40
- cmake --build build_kokkos -j && ctest --test-dir build_kokkos --output-on-failure -LE bench # 91 ctests (92 with `bench`): + device halo / geometry / solver, np=1,2,4,8
40
+ cmake --build build_kokkos -j && ctest --test-dir build_kokkos --output-on-failure -LE bench # 92 ctests (93 with `bench`): + device halo / geometry / solver / VoF PV fit, np=1,2,4,8
41
41
  mpirun -np 4 ./build/benchmarks/bench_halo 48 1 300
42
42
 
43
43
  # Python modules + their ctests (test_mpi.py np=1,2,4,8; state_hash; ndarray interop). Release is
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: peclet-core
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: peclet.core — compatibility shell: peclet.core.geom is now peclet.geom, peclet.core.mpi is now peclet.halo
5
5
  Project-URL: Homepage, https://github.com/computational-chemical-engineering/peclet
6
6
  Project-URL: Documentation, https://computational-chemical-engineering.github.io/peclet/
@@ -5,7 +5,7 @@
5
5
 
6
6
  DOXYFILE_ENCODING = UTF-8
7
7
  PROJECT_NAME = "core"
8
- PROJECT_NUMBER = 1.3.1
8
+ PROJECT_NUMBER = 1.3.2
9
9
  PROJECT_BRIEF = "Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)"
10
10
  OUTPUT_DIRECTORY = docs
11
11
  HTML_OUTPUT = html
@@ -729,7 +729,7 @@ KOKKOS_INLINE_FUNCTION void pvFitInit(PvFit& f) {
729
729
  f.npoly = 0;
730
730
  }
731
731
 
732
- /// Fold one stencil cell's PLIC polygon into the fit.
732
+ /// One stencil cell's PLIC polygon in the PV fit: the parameters of `pvFitTerm` and `pvFitAdd`.
733
733
  ///
734
734
  /// @param mx,my,mz,alpha the cell's PLIC plane in ITS OWN unit-cell coordinates (`plic.hpp`).
735
735
  /// @param off the cell's integer index offset from the target cell.
@@ -744,11 +744,25 @@ KOKKOS_INLINE_FUNCTION void pvFitInit(PvFit& f) {
744
744
  /// integrated with the wrong sign of `z'`. Jibben's area projection
745
745
  /// `A (n.n_p)` decays to zero over the same range; this is the explicit
746
746
  /// version of the same guard.
747
- /// @return true if the polygon contributed.
748
- KOKKOS_INLINE_FUNCTION bool pvFitAdd(PvFit& f, double mx, double my, double mz, double alpha,
749
- const double off[3], const double org[3], const double t1[3],
750
- const double t2[3], const double nn[3], double dW,
751
- double cosMin, const VofMetric& g) {
747
+ ///
748
+ /// `pvFitAdd` is the composition of `pvFitTerm` (the per-polygon map) and `pvFitAccum` (the `+=`
749
+ /// into the normal equations), bitwise (`tests/test_vof_pvfit.cpp`). The split lets a device
750
+ /// fallback evaluate the stencil's terms in parallel and still accumulate them in the canonical
751
+ /// order (flow `doc/vof_step_performance_design.md` §4.7, §5.11).
752
+ struct PvTerm {
753
+ double w, B, s[6];
754
+ bool ok;
755
+ };
756
+
757
+ /// One stencil polygon's contribution to the PV fit: everything `pvFitAdd` computes before the
758
+ /// accumulation — the polygon, the frame transform, the moments `s`, the Wendland weight `w` and
759
+ /// `B`. Same parameters as `pvFitAdd`. Sets `t.ok` and returns it: false exactly where `pvFitAdd`
760
+ /// returns false (a rejected polygon), and then `w`, `B`, `s` are unspecified.
761
+ KOKKOS_INLINE_FUNCTION bool pvFitTerm(PvTerm& t, double mx, double my, double mz, double alpha,
762
+ const double off[3], const double org[3], const double t1[3],
763
+ const double t2[3], const double nn[3], double dW,
764
+ double cosMin, const VofMetric& g) {
765
+ t.ok = false;
752
766
  const double n2 = mx * mx + my * my + mz * mz;
753
767
  if (!(n2 > 0.0))
754
768
  return false;
@@ -804,12 +818,38 @@ KOKKOS_INLINE_FUNCTION bool pvFitAdd(PvFit& f, double mx, double my, double mz,
804
818
  return false;
805
819
 
806
820
  const double B = b0 * s[0] + b1 * s[1] + b2 * s[2];
821
+ t.w = w;
822
+ t.B = B;
823
+ for (int i = 0; i < 6; ++i)
824
+ t.s[i] = s[i];
825
+ t.ok = true;
826
+ return true;
827
+ }
828
+
829
+ /// Fold one accepted term (`t.ok`) into the fit — the accumulation half of `pvFitAdd`, with its
830
+ /// expressions verbatim. Calling it in the stencil's canonical order reproduces `pvFitAdd`'s sums.
831
+ KOKKOS_INLINE_FUNCTION void pvFitAccum(PvFit& f, const PvTerm& t) {
832
+ const double w = t.w, B = t.B;
833
+ const double* s = t.s;
807
834
  for (int i = 0; i < 6; ++i) {
808
835
  f.b[i] += w * s[i] * B;
809
836
  for (int j = 0; j < 6; ++j)
810
837
  f.A[i][j] += w * s[i] * s[j];
811
838
  }
812
839
  ++f.npoly;
840
+ }
841
+
842
+ /// Fold one stencil cell's PLIC polygon into the fit: `pvFitTerm`, then `pvFitAccum` if the
843
+ /// polygon is accepted. Parameters as documented above `PvTerm`.
844
+ /// @return true if the polygon contributed.
845
+ KOKKOS_INLINE_FUNCTION bool pvFitAdd(PvFit& f, double mx, double my, double mz, double alpha,
846
+ const double off[3], const double org[3], const double t1[3],
847
+ const double t2[3], const double nn[3], double dW,
848
+ double cosMin, const VofMetric& g) {
849
+ PvTerm t;
850
+ if (!pvFitTerm(t, mx, my, mz, alpha, off, org, t1, t2, nn, dW, cosMin, g))
851
+ return false;
852
+ pvFitAccum(f, t);
813
853
  return true;
814
854
  }
815
855
 
@@ -19,7 +19,7 @@ build-backend = "hatchling.build"
19
19
 
20
20
  [project]
21
21
  name = "peclet-core"
22
- version = "1.3.1"
22
+ version = "1.3.2"
23
23
  description = "peclet.core — compatibility shell: peclet.core.geom is now peclet.geom, peclet.core.mpi is now peclet.halo"
24
24
  readme = "README.md"
25
25
  requires-python = ">=3.10"
@@ -19,7 +19,7 @@ build-backend = "hatchling.build"
19
19
 
20
20
  [project]
21
21
  name = "peclet-core"
22
- version = "1.3.1"
22
+ version = "1.3.2"
23
23
  description = "peclet.core — compatibility shell: peclet.core.geom is now peclet.geom, peclet.core.mpi is now peclet.halo"
24
24
  readme = "README.md"
25
25
  requires-python = ">=3.10"
@@ -282,6 +282,14 @@ if(MPI_FOUND)
282
282
  target_link_libraries(test_csr_solver PRIVATE Kokkos::kokkos)
283
283
  peclet_core_add_test(csr_solver test_csr_solver)
284
284
 
285
+ # VoF PV fit: pvFitAdd == pvFitTerm + pvFitAccum bitwise, device and host spaces (10^5
286
+ # random cases incl. rejected polygons; flow vof_step_performance_design.md §5.11, WO-7a).
287
+ add_executable(test_vof_pvfit test_vof_pvfit.cpp)
288
+ target_include_directories(test_vof_pvfit PRIVATE
289
+ ${PROJECT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR})
290
+ target_link_libraries(test_vof_pvfit PRIVATE Kokkos::kokkos)
291
+ peclet_core_add_test(vof_pvfit test_vof_pvfit)
292
+
285
293
  # Device GraphAMG apply vs the host oracle (no MPI; serial + device).
286
294
  add_executable(test_graph_amg_device test_graph_amg_device.cpp)
287
295
  target_include_directories(test_graph_amg_device PRIVATE
@@ -0,0 +1,305 @@
1
+ // pvFitAdd == pvFitTerm + pvFitAccum, BITWISE, on the default execution space (CUDA / HIP /
2
+ // OpenMP) and on the host space (flow doc/vof_step_performance_design.md §4.7, §5.11, WO-7a).
3
+ //
4
+ // Three accumulations of the same stencils, each on the same backend:
5
+ // ref a frozen copy of pvFitAdd as it was before the split (below, verbatim body);
6
+ // add today's pvFitAdd (the composition);
7
+ // team the device fallback's shape: one kernel writes every stencil cell's PvTerm to memory,
8
+ // a second kernel folds the accepted terms with pvFitAccum in the canonical order.
9
+ // 10^5 random cases (800 stencils x 5^3 cells), random planes, frames, origins, metrics and guards,
10
+ // with a planted share of rejected polygons (zero normal, plane missing the cell, normal below
11
+ // cosMin, outside the Wendland support). Every accumulator byte and every accept flag must match.
12
+ #include <cstdint>
13
+ #include <cstdio>
14
+ #include <cstring>
15
+ #include <Kokkos_Core.hpp>
16
+ #include <vector>
17
+
18
+ #include "peclet/core/vof/curvature.hpp"
19
+
20
+ using namespace peclet::core::vof;
21
+
22
+ namespace {
23
+
24
+ // --- the pre-split pvFitAdd (core 6859d4e), verbatim apart from its name -------------------------
25
+ KOKKOS_INLINE_FUNCTION bool pvFitAddReference(PvFit& f, double mx, double my, double mz,
26
+ double alpha, const double off[3],
27
+ const double org[3], const double t1[3],
28
+ const double t2[3], const double nn[3], double dW,
29
+ double cosMin, const VofMetric& g) {
30
+ const double n2 = mx * mx + my * my + mz * mz;
31
+ if (!(n2 > 0.0))
32
+ return false;
33
+ // Phase 3 (V2.4): the polygon's own normal enters the fit as a PHYSICAL direction (the frame is
34
+ // physical), so it is pulled back through H^-1 and renormalized. At g.h = {1,1,1} the pullback
35
+ // is `m/1.0` and `invn` is the same reciprocal square root as before — bitwise.
36
+ const double mi[3] = {mx / g.h[0], my / g.h[1], mz / g.h[2]};
37
+ const double invn = 1.0 / Kokkos::sqrt(mi[0] * mi[0] + mi[1] * mi[1] + mi[2] * mi[2]);
38
+ const double np[3] = {(mi[0] * t1[0] + mi[1] * t1[1] + mi[2] * t1[2]) * invn,
39
+ (mi[0] * t2[0] + mi[1] * t2[1] + mi[2] * t2[2]) * invn,
40
+ (mi[0] * nn[0] + mi[1] * nn[1] + mi[2] * nn[2]) * invn};
41
+ if (!(np[2] > cosMin))
42
+ return false;
43
+
44
+ double v[8][3];
45
+ const int nv = plicPolygon(mx, my, mz, alpha, v);
46
+ if (nv < 3)
47
+ return false;
48
+
49
+ // cell-local [0,1]^3 -> target-centred cell units -> the fit frame
50
+ double xy[8][2];
51
+ double zc = 0.0;
52
+ double px = 0.0, py = 0.0;
53
+ for (int k = 0; k < nv; ++k) {
54
+ const double Xi[3] = {off[0] + v[k][0] - 0.5 - org[0], off[1] + v[k][1] - 0.5 - org[1],
55
+ off[2] + v[k][2] - 0.5 - org[2]};
56
+ double X[3];
57
+ g.toPhys(Xi, X); // index displacement -> physical (identity at g.h = {1,1,1})
58
+ xy[k][0] = X[0] * t1[0] + X[1] * t1[1] + X[2] * t1[2];
59
+ xy[k][1] = X[0] * t2[0] + X[1] * t2[1] + X[2] * t2[2];
60
+ const double z = X[0] * nn[0] + X[1] * nn[1] + X[2] * nn[2];
61
+ px += xy[k][0];
62
+ py += xy[k][1];
63
+ zc += z;
64
+ }
65
+ const double invv = 1.0 / static_cast<double>(nv);
66
+ px *= invv;
67
+ py *= invv;
68
+ zc *= invv;
69
+
70
+ double s[6];
71
+ polygonMoments2d(xy, nv, s);
72
+ if (!(Kokkos::fabs(s[0]) > 1e-14))
73
+ return false;
74
+
75
+ // the polygon's own plane in the fit frame: z' = b0 + b1 x' + b2 y'
76
+ const double b1 = -np[0] / np[2], b2 = -np[1] / np[2];
77
+ const double b0 = zc - b1 * px - b2 * py;
78
+
79
+ const double r = Kokkos::sqrt(px * px + py * py + zc * zc);
80
+ const double w = wendlandWeight(r, dW);
81
+ if (!(w > 0.0))
82
+ return false;
83
+
84
+ const double B = b0 * s[0] + b1 * s[1] + b2 * s[2];
85
+ for (int i = 0; i < 6; ++i) {
86
+ f.b[i] += w * s[i] * B;
87
+ for (int j = 0; j < 6; ++j)
88
+ f.A[i][j] += w * s[i] * s[j];
89
+ }
90
+ ++f.npoly;
91
+ return true;
92
+ }
93
+
94
+ constexpr int kSide = 5, kCells = kSide * kSide * kSide, kStencils = 800;
95
+ constexpr int kCases = kStencils * kCells; // 100000
96
+
97
+ struct Stencil {
98
+ double org[3], t1[3], t2[3], nn[3], dW, cosMin;
99
+ VofMetric g;
100
+ };
101
+ struct Cell {
102
+ double mx, my, mz, alpha, off[3];
103
+ };
104
+
105
+ struct Rng { // splitmix64: fixed seed, identical sequence on every host
106
+ std::uint64_t s;
107
+ std::uint64_t next() {
108
+ std::uint64_t z = (s += 0x9e3779b97f4a7c15ULL);
109
+ z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
110
+ z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
111
+ return z ^ (z >> 31);
112
+ }
113
+ double uni() { return (next() >> 11) * 0x1.0p-53; } // [0,1)
114
+ double sym() { return 2.0 * uni() - 1.0; } // [-1,1)
115
+ };
116
+
117
+ void unit(double v[3]) {
118
+ const double n = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
119
+ for (int a = 0; a < 3; ++a)
120
+ v[a] /= n;
121
+ }
122
+
123
+ void makeCases(std::vector<Stencil>& st, std::vector<Cell>& ce) {
124
+ Rng r{20261002ULL};
125
+ st.resize(kStencils);
126
+ ce.resize(kCases);
127
+ for (int s = 0; s < kStencils; ++s) {
128
+ Stencil& S = st[s];
129
+ double nn[3] = {r.sym(), r.sym(), r.sym()};
130
+ unit(nn);
131
+ // t1 = any unit vector orthogonal to nn, t2 = nn x t1
132
+ double a[3] = {1, 0, 0};
133
+ if (std::fabs(nn[0]) > 0.6)
134
+ a[0] = 0, a[1] = 1;
135
+ const double d = a[0] * nn[0] + a[1] * nn[1] + a[2] * nn[2];
136
+ double t1[3] = {a[0] - d * nn[0], a[1] - d * nn[1], a[2] - d * nn[2]};
137
+ unit(t1);
138
+ const double t2[3] = {nn[1] * t1[2] - nn[2] * t1[1], nn[2] * t1[0] - nn[0] * t1[2],
139
+ nn[0] * t1[1] - nn[1] * t1[0]};
140
+ for (int k = 0; k < 3; ++k) {
141
+ S.nn[k] = nn[k];
142
+ S.t1[k] = t1[k];
143
+ S.t2[k] = t2[k];
144
+ S.org[k] = 0.5 * r.sym();
145
+ }
146
+ S.dW = (s % 50 == 7) ? 0.0 : 1.5 + 2.0 * r.uni(); // a few unweighted stencils (d <= 0)
147
+ const double cm[4] = {0.0, 0.2, 0.5, -1.0};
148
+ S.cosMin = cm[s % 4];
149
+ if (s % 3 != 0) // two thirds anisotropic
150
+ for (int k = 0; k < 3; ++k)
151
+ S.g.h[k] = 0.5 + 1.5 * r.uni();
152
+ for (int k = 0; k < kCells; ++k) {
153
+ Cell& C = ce[s * kCells + k];
154
+ C.off[0] = k % kSide - 2;
155
+ C.off[1] = (k / kSide) % kSide - 2;
156
+ C.off[2] = k / (kSide * kSide) - 2;
157
+ const int kind = static_cast<int>(r.next() % 20);
158
+ if (kind == 0) { // degenerate normal
159
+ C.mx = C.my = C.mz = 0.0;
160
+ } else if (kind <= 3) { // random direction (many fall below cosMin)
161
+ C.mx = r.sym(), C.my = r.sym(), C.mz = r.sym();
162
+ } else { // near the target normal, in index space
163
+ C.mx = nn[0] * S.g.h[0] + 0.3 * r.sym();
164
+ C.my = nn[1] * S.g.h[1] + 0.3 * r.sym();
165
+ C.mz = nn[2] * S.g.h[2] + 0.3 * r.sym();
166
+ }
167
+ if (kind == 1) { // plane well outside the cube: no polygon
168
+ C.alpha = 3.0 + r.uni();
169
+ } else { // plane through a random point of the cell
170
+ const double p[3] = {r.uni(), r.uni(), r.uni()};
171
+ C.alpha = C.mx * p[0] + C.my * p[1] + C.mz * p[2];
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ struct Result {
178
+ std::vector<PvFit> ref, add, team;
179
+ std::vector<int> fref, fadd, fterm;
180
+ };
181
+
182
+ template <class Exec>
183
+ Result run(const std::vector<Stencil>& st, const std::vector<Cell>& ce) {
184
+ using Mem = typename Exec::memory_space;
185
+ Kokkos::View<Stencil*, Mem> S("st", kStencils);
186
+ Kokkos::View<Cell*, Mem> C("ce", kCases);
187
+ {
188
+ auto hs = Kokkos::create_mirror_view(S);
189
+ auto hc = Kokkos::create_mirror_view(C);
190
+ for (int i = 0; i < kStencils; ++i)
191
+ hs(i) = st[i];
192
+ for (int i = 0; i < kCases; ++i)
193
+ hc(i) = ce[i];
194
+ Kokkos::deep_copy(S, hs);
195
+ Kokkos::deep_copy(C, hc);
196
+ }
197
+ Kokkos::View<PvFit*, Mem> ref("ref", kStencils), add("add", kStencils), team("team", kStencils);
198
+ Kokkos::View<int*, Mem> fref("fref", kCases), fadd("fadd", kCases), fterm("fterm", kCases);
199
+ Kokkos::View<PvTerm*, Mem> terms("terms", kCases);
200
+
201
+ // ref and add: one thread per stencil, cells in canonical order (as curvFallbackCell does).
202
+ // Separate kernels, so the compiler cannot merge the two identical computations into one.
203
+ Kokkos::parallel_for(
204
+ "pvfit_ref", Kokkos::RangePolicy<Exec>(0, kStencils), KOKKOS_LAMBDA(const int s) {
205
+ const Stencil& T = S(s);
206
+ PvFit f;
207
+ pvFitInit(f);
208
+ for (int k = 0; k < kCells; ++k) {
209
+ const Cell& c = C(s * kCells + k);
210
+ fref(s * kCells + k) = pvFitAddReference(f, c.mx, c.my, c.mz, c.alpha, c.off, T.org, T.t1,
211
+ T.t2, T.nn, T.dW, T.cosMin, T.g);
212
+ }
213
+ ref(s) = f;
214
+ });
215
+ Kokkos::parallel_for(
216
+ "pvfit_add", Kokkos::RangePolicy<Exec>(0, kStencils), KOKKOS_LAMBDA(const int s) {
217
+ const Stencil& T = S(s);
218
+ PvFit f;
219
+ pvFitInit(f);
220
+ for (int k = 0; k < kCells; ++k) {
221
+ const Cell& c = C(s * kCells + k);
222
+ fadd(s * kCells + k) = pvFitAdd(f, c.mx, c.my, c.mz, c.alpha, c.off, T.org, T.t1, T.t2,
223
+ T.nn, T.dW, T.cosMin, T.g);
224
+ }
225
+ add(s) = f;
226
+ });
227
+ // team shape: the map over every case, then the ordered fold
228
+ Kokkos::parallel_for(
229
+ "pvfit_term", Kokkos::RangePolicy<Exec>(0, kCases), KOKKOS_LAMBDA(const int i) {
230
+ const Stencil& T = S(i / kCells);
231
+ const Cell& c = C(i);
232
+ PvTerm t;
233
+ fterm(i) = pvFitTerm(t, c.mx, c.my, c.mz, c.alpha, c.off, T.org, T.t1, T.t2, T.nn, T.dW,
234
+ T.cosMin, T.g);
235
+ terms(i) = t;
236
+ });
237
+ Kokkos::parallel_for(
238
+ "pvfit_accum", Kokkos::RangePolicy<Exec>(0, kStencils), KOKKOS_LAMBDA(const int s) {
239
+ PvFit f;
240
+ pvFitInit(f);
241
+ for (int k = 0; k < kCells; ++k)
242
+ if (terms(s * kCells + k).ok)
243
+ pvFitAccum(f, terms(s * kCells + k));
244
+ team(s) = f;
245
+ });
246
+ Kokkos::fence();
247
+
248
+ Result R;
249
+ auto pull = [](auto v, auto& out) {
250
+ auto h = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), v);
251
+ out.assign(h.data(), h.data() + h.extent(0));
252
+ };
253
+ pull(ref, R.ref);
254
+ pull(add, R.add);
255
+ pull(team, R.team);
256
+ pull(fref, R.fref);
257
+ pull(fadd, R.fadd);
258
+ pull(fterm, R.fterm);
259
+ return R;
260
+ }
261
+
262
+ bool sameFit(const PvFit& a, const PvFit& b) {
263
+ return std::memcmp(a.A, b.A, sizeof a.A) == 0 && std::memcmp(a.b, b.b, sizeof a.b) == 0 &&
264
+ a.npoly == b.npoly;
265
+ }
266
+
267
+ int check(const char* name, const Result& R) {
268
+ int badFit = 0, badFlag = 0, accepted = 0, fitted = 0;
269
+ for (int s = 0; s < kStencils; ++s) {
270
+ badFit += !sameFit(R.ref[s], R.add[s]) || !sameFit(R.ref[s], R.team[s]);
271
+ fitted += R.ref[s].npoly >= 6;
272
+ }
273
+ for (int i = 0; i < kCases; ++i) {
274
+ badFlag += (R.fref[i] != R.fadd[i]) || (R.fref[i] != R.fterm[i]);
275
+ accepted += R.fref[i];
276
+ }
277
+ const int rejected = kCases - accepted;
278
+ std::printf(
279
+ "[%s] %d cases: %d accepted, %d rejected; %d/%d stencils with npoly >= 6; "
280
+ "accumulator mismatches %d, flag mismatches %d\n",
281
+ name, kCases, accepted, rejected, fitted, kStencils, badFit, badFlag);
282
+ // non-vacuous: both branches exercised in volume, and most stencils carry a real fit
283
+ const bool coverage = accepted > kCases / 5 && rejected > kCases / 20 && fitted > kStencils / 2;
284
+ if (!coverage)
285
+ std::printf("[%s] FAIL: coverage too thin to mean anything\n", name);
286
+ return badFit + badFlag + (coverage ? 0 : 1);
287
+ }
288
+
289
+ } // namespace
290
+
291
+ int main(int argc, char** argv) {
292
+ Kokkos::initialize(argc, argv);
293
+ int bad = 0;
294
+ {
295
+ std::vector<Stencil> st;
296
+ std::vector<Cell> ce;
297
+ makeCases(st, ce);
298
+ bad += check(Kokkos::DefaultExecutionSpace::name(), run<Kokkos::DefaultExecutionSpace>(st, ce));
299
+ bad += check(Kokkos::DefaultHostExecutionSpace::name(),
300
+ run<Kokkos::DefaultHostExecutionSpace>(st, ce));
301
+ }
302
+ Kokkos::finalize();
303
+ std::printf(bad ? "FAIL\n" : "PASS\n");
304
+ return bad ? 1 : 0;
305
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes